This is an automated email from the ASF dual-hosted git repository.

xiedeyantu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 55573d6dbe [CALCITE-7623] SemiJoinProjectTransposeRule should support 
ANTI joins
55573d6dbe is described below

commit 55573d6dbe54ad93036e6f265506c980d5283894
Author: Zhen Chen <[email protected]>
AuthorDate: Thu Jun 25 11:48:25 2026 +0800

    [CALCITE-7623] SemiJoinProjectTransposeRule should support ANTI joins
---
 .../rel/rules/SemiJoinProjectTransposeRule.java    | 80 +++++++++++++---------
 .../org/apache/calcite/test/RelOptRulesTest.java   | 20 ++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 18 +++++
 3 files changed, 84 insertions(+), 34 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
index 42deabf5e3..ea4e38633f 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinProjectTransposeRule.java
@@ -46,14 +46,19 @@
 
 /**
  * Planner rule that pushes
- * a {@link Join#isSemiJoin semi-join} down in a tree past
+ * a semi-join or anti-join down in a tree past
  * a {@link org.apache.calcite.rel.core.Project}.
  *
  * <p>The intention is to trigger other rules that will convert
- * {@code SemiJoin}s.
+ * {@code SemiJoin}s or {@code AntiJoin}s.
  *
  * <p>SemiJoin(LogicalProject(X), Y) &rarr; LogicalProject(SemiJoin(X, Y))
  *
+ * <p>AntiJoin(LogicalProject(X), Y) &rarr; LogicalProject(AntiJoin(X, Y))
+ *
+ * <p>This rule only transposes a project on the left input. Semi-joins and
+ * anti-joins only project fields from their left input.
+ *
  * @see org.apache.calcite.rel.rules.SemiJoinFilterTransposeRule
  */
 @Value.Enclosing
@@ -69,56 +74,56 @@ protected SemiJoinProjectTransposeRule(Config config) {
   //~ Methods ----------------------------------------------------------------
 
   @Override public void onMatch(RelOptRuleCall call) {
-    final Join semiJoin = call.rel(0);
+    final Join join = call.rel(0);
     final Project project = call.rel(1);
 
-    // Convert the LHS semi-join keys to reference the child projection
-    // expression; all projection expressions must be RexInputRefs,
-    // otherwise, we wouldn't have created this semi-join.
+    // Convert the LHS semi-join or anti-join keys to reference the child
+    // projection expression; all projection expressions must be RexInputRefs,
+    // otherwise, we wouldn't have created this semi-join or anti-join.
 
-    // convert the semijoin condition to reflect the LHS with the project
+    // convert the join condition to reflect the LHS with the project
     // pulled up
-    RexNode newCondition = adjustCondition(project, semiJoin);
+    RexNode newCondition = adjustCondition(project, join);
 
-    LogicalJoin newSemiJoin =
+    LogicalJoin newJoin =
         LogicalJoin.create(project.getInput(),
-            semiJoin.getRight(),
+            join.getRight(),
             // No need to copy the hints, the framework would try to do that.
             ImmutableList.of(),
             newCondition,
-            ImmutableSet.of(), JoinRelType.SEMI);
+            ImmutableSet.of(), join.getJoinType());
 
     // Create the new projection.  Note that the projection expressions
     // are the same as the original because they only reference the LHS
-    // of the semijoin and the semijoin only projects out the LHS
+    // of the semi-join or anti-join, which only projects out the LHS.
     final RelBuilder relBuilder = call.builder();
-    relBuilder.push(newSemiJoin);
+    relBuilder.push(newJoin);
     relBuilder.project(project.getProjects(), 
project.getRowType().getFieldNames());
 
     call.transformTo(relBuilder.build());
   }
 
   /**
-   * Pulls the project above the semijoin and returns the resulting semijoin
-   * condition. As a result, the semijoin condition should be modified such
-   * that references to the LHS of a semijoin should now reference the
+   * Pulls the project above the semi-join or anti-join and returns the 
resulting
+   * join condition. As a result, the join condition should be modified such
+   * that references to the LHS of the join should now reference the
    * children of the project that's on the LHS.
    *
-   * @param project  LogicalProject on the LHS of the semijoin
-   * @param semiJoin the semijoin
-   * @return the modified semijoin condition
+   * @param project  LogicalProject on the LHS of the join
+   * @param join the semi-join or anti-join
+   * @return the modified join condition
    */
-  private static RexNode adjustCondition(Project project, Join semiJoin) {
+  private static RexNode adjustCondition(Project project, Join join) {
     // create two RexPrograms -- the bottom one representing a
-    // concatenation of the project and the RHS of the semijoin and the
-    // top one representing the semijoin condition
+    // concatenation of the project and the RHS of the join and the
+    // top one representing the join condition
 
     final RexBuilder rexBuilder = project.getCluster().getRexBuilder();
     final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
-    final RelNode rightChild = semiJoin.getRight();
+    final RelNode rightChild = join.getRight();
 
     // for the bottom RexProgram, the input is a concatenation of the
-    // child of the project and the RHS of the semijoin
+    // child of the project and the RHS of the join
     RelDataType bottomInputRowType =
         SqlValidatorUtil.deriveJoinRowType(
             project.getInput().getRowType(),
@@ -126,12 +131,12 @@ private static RexNode adjustCondition(Project project, 
Join semiJoin) {
             JoinRelType.INNER,
             typeFactory,
             null,
-            semiJoin.getSystemFieldList());
+            join.getSystemFieldList());
     RexProgramBuilder bottomProgramBuilder =
         new RexProgramBuilder(bottomInputRowType, rexBuilder);
 
     // add the project expressions, then add input references for the RHS
-    // of the semijoin
+    // of the join
     for (Pair<RexNode, String> pair : project.getNamedProjects()) {
       bottomProgramBuilder.addProject(pair.left, pair.right);
     }
@@ -148,8 +153,8 @@ private static RexNode adjustCondition(Project project, 
Join semiJoin) {
     }
     RexProgram bottomProgram = bottomProgramBuilder.getProgram();
 
-    // input rowtype into the top program is the concatenation of the
-    // project and the RHS of the semijoin
+    // input row type into the top program is the concatenation of the
+    // project and the RHS of the join
     RelDataType topInputRowType =
         SqlValidatorUtil.deriveJoinRowType(
             project.getRowType(),
@@ -157,18 +162,18 @@ private static RexNode adjustCondition(Project project, 
Join semiJoin) {
             JoinRelType.INNER,
             typeFactory,
             null,
-            semiJoin.getSystemFieldList());
+            join.getSystemFieldList());
     RexProgramBuilder topProgramBuilder =
         new RexProgramBuilder(
             topInputRowType,
             rexBuilder);
     topProgramBuilder.addIdentity();
-    topProgramBuilder.addCondition(semiJoin.getCondition());
+    topProgramBuilder.addCondition(join.getCondition());
     RexProgram topProgram = topProgramBuilder.getProgram();
 
     // merge the programs and expand out the local references to form
-    // the new semijoin condition; it now references a concatenation of
-    // the project's child and the RHS of the semijoin
+    // the new join condition; it now references a concatenation of
+    // the project's child and the RHS of the join
     RexProgram mergedProgram =
         RexProgramBuilder.mergePrograms(
             topProgram,
@@ -180,6 +185,11 @@ private static RexNode adjustCondition(Project project, 
Join semiJoin) {
             () -> "mergedProgram.getCondition() for " + mergedProgram));
   }
 
+  private static boolean isSemiOrAntiJoin(Join join) {
+    return join.getJoinType() == JoinRelType.SEMI
+        || join.getJoinType() == JoinRelType.ANTI;
+  }
+
   /** Rule configuration. */
   @Value.Immutable
   public interface Config extends RelRule.Config {
@@ -194,8 +204,10 @@ public interface Config extends RelRule.Config {
     default Config withOperandFor(Class<? extends Join> joinClass,
         Class<? extends Project> projectClass) {
       return withOperandSupplier(b ->
-          b.operand(joinClass).predicate(Join::isSemiJoin).inputs(b2 ->
-              b2.operand(projectClass).anyInputs()))
+          b.operand(joinClass)
+              .predicate(SemiJoinProjectTransposeRule::isSemiOrAntiJoin)
+              .inputs(b2 ->
+                  b2.operand(projectClass).anyInputs()))
           .as(Config.class);
     }
   }
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index b10a5c065c..88080fad47 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -1498,6 +1498,26 @@ private RelOptFixture 
basePushFilterPastAggWithGroupingSets() {
     checkSemiOrAntiJoinProjectTranspose(JoinRelType.ANTI);
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7623";>[CALCITE-7623]
+   * SemiJoinProjectTransposeRule should support ANTI joins</a>. */
+  @Test void testSemiJoinProjectTransposeSupportsAntiJoin() {
+    final Function<RelBuilder, RelNode> relFn = b -> {
+      RelNode left = b.scan("DEPT")
+          .project(b.field("DNAME"), b.field("DEPTNO"))
+          .build();
+      RelNode right = b.scan("EMP").build();
+
+      return b.push(left)
+          .push(right)
+          .join(JoinRelType.ANTI,
+              b.equals(b.field(2, 0, "DEPTNO"),
+                  b.field(2, 1, "DEPTNO")))
+          .build();
+    };
+    relFn(relFn).withRule(CoreRules.SEMI_JOIN_PROJECT_TRANSPOSE).check();
+  }
+
   private void checkSemiOrAntiJoinProjectTranspose(JoinRelType type) {
     final Function<RelBuilder, RelNode> relFn = b -> {
       RelNode left = b.scan("DEPT").build();
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 2a47eefd0d..e8d6bcd229 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -17989,6 +17989,24 @@ LogicalProject(DNAME=[$1])
       LogicalAggregate(group=[{0}])
         LogicalProject($f0=[*(2, $0)])
           LogicalTableScan(table=[[scott, DEPT]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testSemiJoinProjectTransposeSupportsAntiJoin">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalJoin(condition=[=($1, $9)], joinType=[anti])
+  LogicalProject(DNAME=[$1], DEPTNO=[$0])
+    LogicalTableScan(table=[[scott, DEPT]])
+  LogicalTableScan(table=[[scott, EMP]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DNAME=[$1], DEPTNO=[$0])
+  LogicalJoin(condition=[=($0, $10)], joinType=[anti])
+    LogicalTableScan(table=[[scott, DEPT]])
+    LogicalTableScan(table=[[scott, EMP]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to