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

mbudiu 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 69bfec3dd2 [CALCITE-6904] IS_NOT_DISTINCT_FROM is incorrectly handled 
by EnumerableJoinRule
69bfec3dd2 is described below

commit 69bfec3dd298a81afb4c1f43f7520ab6f74c6a59
Author: Zhen Chen <[email protected]>
AuthorDate: Tue Apr 8 06:58:23 2025 +0800

    [CALCITE-6904] IS_NOT_DISTINCT_FROM is incorrectly handled by 
EnumerableJoinRule
---
 .../calcite/adapter/enumerable/RexImpTable.java    | 47 ++++++++++++++++++++++
 .../java/org/apache/calcite/rel/core/Join.java     |  2 +-
 .../java/org/apache/calcite/rel/core/JoinInfo.java | 18 ++++++++-
 .../calcite/rel/rules/LoptOptimizeJoinRule.java    |  2 +-
 .../calcite/rel/rules/LoptSemiJoinOptimizer.java   |  2 +-
 .../calcite/rel/rules/SortJoinTransposeRule.java   |  2 +-
 .../java/org/apache/calcite/test/JdbcTest.java     | 18 +++++++++
 core/src/test/resources/sql/join.iq                | 21 ++++++++++
 8 files changed, 107 insertions(+), 5 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index d5b752dace..3364459261 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -425,6 +425,7 @@
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_SCALAR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_VALUE;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_A_SET;
+import static 
org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_DISTINCT_FROM;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_EMPTY;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_FALSE;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_ARRAY;
@@ -1008,6 +1009,7 @@ void populate2() {
       define(IS_NOT_TRUE, new IsNotTrueImplementor());
       define(IS_FALSE, new IsFalseImplementor());
       define(IS_NOT_FALSE, new IsNotFalseImplementor());
+      define(IS_NOT_DISTINCT_FROM, new IsNotDistinctFromImplementor());
 
       // LIKE, ILIKE, RLIKE and SIMILAR
       defineReflective(LIKE, BuiltInMethod.LIKE.method,
@@ -4741,6 +4743,51 @@ private static class IsNullImplementor extends 
AbstractRexCallImplementor {
     }
   }
 
+  /** Implementor for the {@code IS NOT DISTINCT FROM} SQL operator. */
+  private static class IsNotDistinctFromImplementor extends 
AbstractRexCallImplementor {
+    IsNotDistinctFromImplementor() {
+      super("is_not_distinct_from", NullPolicy.NONE, false);
+    }
+
+    @Override public RexToLixTranslator.Result implement(final 
RexToLixTranslator translator,
+        final RexCall call, final List<RexToLixTranslator.Result> arguments) {
+      final RexToLixTranslator.Result left = arguments.get(0);
+      final RexToLixTranslator.Result right = arguments.get(1);
+
+      // Generated expression:
+      // left IS NULL ?
+      //   (right IS NULL ? TRUE : FALSE) :  -> when left is null
+      //   (right IS NULL ? FALSE :          -> when left is not null
+      //     left.equals(right))             -> when both are not null, 
compare values
+      final Expression valueExpression =
+          Expressions.condition(left.isNullVariable,
+          Expressions.condition(right.isNullVariable, BOXED_TRUE_EXPR, 
BOXED_FALSE_EXPR),
+          Expressions.condition(right.isNullVariable, BOXED_FALSE_EXPR,
+              Expressions.equal(left.valueVariable, right.valueVariable)));
+
+      BlockBuilder builder = translator.getBlockBuilder();
+      final ParameterExpression valueVariable =
+          Expressions.parameter(valueExpression.getType(),
+              builder.newName(variableName + "_value"));
+      final ParameterExpression isNullVariable =
+          Expressions.parameter(Boolean.TYPE,
+              builder.newName(variableName + "_isNull"));
+
+      builder.add(
+          Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+      builder.add(
+          Expressions.declare(Modifier.FINAL, isNullVariable, FALSE_EXPR));
+
+      return new RexToLixTranslator.Result(isNullVariable, valueVariable);
+    }
+
+    @Override Expression implementSafe(final RexToLixTranslator translator,
+        final RexCall call, final List<Expression> argValueList) {
+      throw new IllegalStateException("This implementSafe should not be 
called,"
+          + " please call implement(...)");
+    }
+  }
+
   /** Implementor for the {@code IS TRUE} SQL operator. */
   private static class IsTrueImplementor extends AbstractRexCallImplementor {
     IsTrueImplementor() {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Join.java 
b/core/src/main/java/org/apache/calcite/rel/core/Join.java
index 540c5806be..8016e9f36a 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Join.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Join.java
@@ -104,7 +104,7 @@ protected Join(
     this.condition = requireNonNull(condition, "condition");
     this.variablesSet = ImmutableSet.copyOf(variablesSet);
     this.joinType = requireNonNull(joinType, "joinType");
-    this.joinInfo = JoinInfo.of(left, right, condition);
+    this.joinInfo = JoinInfo.createWithStrictEquality(left, right, condition);
     this.hints = ImmutableList.copyOf(hints);
   }
 
diff --git a/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java 
b/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
index 2a33714838..f092a73ea9 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/JoinInfo.java
@@ -38,7 +38,7 @@
  * <p>It is useful for the many algorithms that care whether a join has an
  * equi-join condition.
  *
- * <p>You can create one using {@link #of}, or call
+ * <p>You can create one using {@link #createWithStrictEquality}, or call
  * {@link Join#analyzeCondition()}; many kinds of join cache their
  * join info, especially those that are equi-joins.
  *
@@ -70,6 +70,22 @@ public static JoinInfo of(RelNode left, RelNode right, 
RexNode condition) {
         ImmutableIntList.copyOf(rightKeys), ImmutableList.copyOf(nonEquiList));
   }
 
+  /** Creates a {@code JoinInfo} by analyzing a condition.
+   * A JoinInfo created using this method only considers
+   * EQUALS operations equi-conditions; in contrast
+   * IS_NOT_DISTINCT_FROM operations are NOT considered equalities;
+   * they are inserted in the nonEquiList. */
+  public static JoinInfo createWithStrictEquality(RelNode left,
+      RelNode right, RexNode condition) {
+    final List<Integer> leftKeys = new ArrayList<>();
+    final List<Integer> rightKeys = new ArrayList<>();
+    final List<RexNode> nonEquiList = new ArrayList<>();
+    RelOptUtil.splitJoinCondition(left, right, condition, leftKeys, rightKeys,
+        null, nonEquiList);
+    return new JoinInfo(ImmutableIntList.copyOf(leftKeys),
+        ImmutableIntList.copyOf(rightKeys), ImmutableList.copyOf(nonEquiList));
+  }
+
   /** Creates an equi-join. */
   public static JoinInfo of(ImmutableIntList leftKeys,
       ImmutableIntList rightKeys) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
index 16cb367ac2..75056dd981 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptOptimizeJoinRule.java
@@ -2053,7 +2053,7 @@ public static boolean isRemovableSelfJoin(Join joinRel) {
    */
   private static boolean areSelfJoinKeysUnique(RelMetadataQuery mq,
       RelNode leftRel, RelNode rightRel, RexNode joinFilters) {
-    final JoinInfo joinInfo = JoinInfo.of(leftRel, rightRel, joinFilters);
+    final JoinInfo joinInfo = JoinInfo.createWithStrictEquality(leftRel, 
rightRel, joinFilters);
 
     // Make sure each key on the left maps to the same simple column as the
     // corresponding key on the right
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java 
b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
index 724884641d..e9d5020b0d 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/LoptSemiJoinOptimizer.java
@@ -264,7 +264,7 @@ private static int isSuitableFilter(
 
     RelNode factRel = multiJoin.getJoinFactor(factIdx);
     RelNode dimRel = multiJoin.getJoinFactor(dimIdx);
-    final JoinInfo joinInfo = JoinInfo.of(factRel, dimRel, semiJoinCondition);
+    final JoinInfo joinInfo = JoinInfo.createWithStrictEquality(factRel, 
dimRel, semiJoinCondition);
     assert !joinInfo.leftKeys.isEmpty();
 
     // mutable copies
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
index e3d2d5b3c5..c59437a5b8 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortJoinTransposeRule.java
@@ -77,7 +77,7 @@ public SortJoinTransposeRule(Class<? extends Sort> sortClass,
     final Join join = call.rel(1);
     final RelMetadataQuery mq = call.getMetadataQuery();
     final JoinInfo joinInfo =
-        JoinInfo.of(join.getLeft(), join.getRight(), join.getCondition());
+        JoinInfo.createWithStrictEquality(join.getLeft(), join.getRight(), 
join.getCondition());
 
     // 1) If join is not a left or right outer, we bail out
     // 2) If sort is not a trivial order-by, and if there is
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index e5c861babc..ac1948f2e6 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -3950,6 +3950,24 @@ public void checkOrderBy(final boolean desc,
         .returnsUnordered("empid=150; name=Sebastian");
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6904";>[CALCITE-6904]
+   * IS_NOT_DISTINCT_FROM is converted error in EnumerableJoinRule</a>. */
+  @Test void testIsNotDistinctFrom() {
+    final String sql = ""
+        + "select \"t1\".\"commission\" from \"hr\".\"emps\" as \"t1\"\n"
+        + "join\n"
+        + "\"hr\".\"emps\" as \"t2\"\n"
+        + "on \"t1\".\"commission\" is not distinct from 
\"t2\".\"commission\"";
+    CalciteAssert.hr()
+        .query(sql)
+        .explainContains("NestedLoopJoin(condition=[IS NOT DISTINCT FROM($0, 
$1)]")
+        .returnsUnordered("commission=1000",
+            "commission=250",
+            "commission=500",
+            "commission=null");
+  }
+
   @Test void testExcept() {
     final String sql = ""
         + "select \"empid\", \"name\" from \"hr\".\"emps\" where 
\"deptno\"=10\n"
diff --git a/core/src/test/resources/sql/join.iq 
b/core/src/test/resources/sql/join.iq
index 6330a8d746..a5ee6eea15 100644
--- a/core/src/test/resources/sql/join.iq
+++ b/core/src/test/resources/sql/join.iq
@@ -833,4 +833,25 @@ JOIN (VALUES (3, 7), (6, 14), (9, 21), (12, 28), (9, 35)) 
AS t3(b, d) USING (b);
 
 !ok
 
+SELECT t1.a
+FROM (VALUES (1), (2)) AS t1(a)
+JOIN (VALUES (1), (null)) AS t2(a)
+ON t1.a IS DISTINCT FROM t2.a;
++---+
+| A |
++---+
+| 1 |
+| 2 |
+| 2 |
++---+
+(3 rows)
+
+!ok
+
+EnumerableCalc(expr#0..1=[{inputs}], A=[$t0])
+  EnumerableNestedLoopJoin(condition=[IS NOT TRUE(=($0, $1))], 
joinType=[inner])
+    EnumerableValues(tuples=[[{ 1 }, { 2 }]])
+    EnumerableValues(tuples=[[{ 1 }, { null }]])
+!plan
+
 # End join.iq

Reply via email to