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

zabetak 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 43e0d7581f [CALCITE-7145] RexSimplify should not simplify IS NULL(10/0)
43e0d7581f is described below

commit 43e0d7581f3247354686aea83400abe4490be2d3
Author: Thomas Rebele <[email protected]>
AuthorDate: Mon Aug 25 18:24:38 2025 +0200

    [CALCITE-7145] RexSimplify should not simplify IS NULL(10/0)
---
 .../java/org/apache/calcite/rex/RexSimplify.java   | 12 ++++-
 .../org/apache/calcite/rex/RexProgramTest.java     | 56 ++++++++++++++++++++--
 .../org/apache/calcite/test/RelOptRulesTest.java   | 13 +++--
 .../org/apache/calcite/test/RelOptRulesTest.xml    |  2 +-
 .../org/apache/calcite/test/DruidAdapter2IT.java   |  2 +
 .../org/apache/calcite/test/DruidAdapterIT.java    |  2 +
 6 files changed, 75 insertions(+), 12 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java 
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 3f78607494..2ad299675f 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -1143,7 +1143,8 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     // "(CASE WHEN FALSE THEN 1 ELSE 2) IS NOT NULL" we first simplify the
     // argument to "2", and only then we can simplify "2 IS NOT NULL" to 
"TRUE".
     a = simplify(a, UNKNOWN);
-    if (!a.getType().isNullable() && isSafeExpression(a)) {
+    boolean isSafe = isSafeExpression(a);
+    if (!a.getType().isNullable() && isSafe) {
       return rexBuilder.makeLiteral(true);
     }
     if (RexUtil.isLosslessCast(a)) {
@@ -1158,6 +1159,9 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     if (hasCustomNullabilityRules(a.getKind())) {
       return null;
     }
+    if (!isSafe) {
+      return rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, a);
+    }
     switch (Strong.policy(a)) {
     case NOT_NULL:
       return rexBuilder.makeLiteral(true);
@@ -1198,7 +1202,8 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     // "(CASE WHEN FALSE THEN 1 ELSE 2) IS NULL" we first simplify the
     // argument to "2", and only then we can simplify "2 IS NULL" to "FALSE".
     a = simplify(a, UNKNOWN);
-    if (!a.getType().isNullable() && isSafeExpression(a)) {
+    boolean isSafe = isSafeExpression(a);
+    if (!a.getType().isNullable() && isSafe) {
       return rexBuilder.makeLiteral(false);
     }
     if (RexUtil.isLosslessCast(a)) {
@@ -1213,6 +1218,9 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     if (hasCustomNullabilityRules(a.getKind())) {
       return null;
     }
+    if (!isSafe) {
+      return rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, a);
+    }
     switch (Strong.policy(a)) {
     case NOT_NULL:
       return rexBuilder.makeLiteral(false);
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index 22d84ed4f0..91f55624e8 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -2715,6 +2715,7 @@ trueLiteral, literal(1),
     // null + (a/0)/4
     // ==>
     // null + (a/0)/4
+    // a/0 throws an exception so it may not be simplified
     RexNode divideNode0 = plus(nullInt, div(div(vIntNotNull(), literal(0)), 
literal(4)));
     checkSimplifyUnchanged(divideNode0);
     // null + a/4
@@ -2730,13 +2731,56 @@ trueLiteral, literal(1),
     // null + null/0
     // ==>
     // null + null/0
-    RexNode divideNode3 = plus(nullInt, div(vIntNotNull(), literal(0)));
+    RexNode divideNode3 = plus(nullInt, div(nullInt, literal(0)));
     checkSimplifyUnchanged(divideNode3);
+    // null + a/0
+    // ==>
+    // null + a/0
+    RexNode divideNode4 = plus(nullInt, div(vIntNotNull(), literal(0)));
+    checkSimplifyUnchanged(divideNode4);
     // null + a/b
     // ==>
     // null + a/b
-    RexNode divideNode4 = plus(nullInt, div(vIntNotNull(), vIntNotNull()));
-    checkSimplifyUnchanged(divideNode4);
+    // b might be 0 and throw an exception, so a/b cannot be simplified.
+    // E.g., PostgreSQL throws a division-by-zero error for the following 
query:
+    // SELECT NULL + (a/b) FROM (select 1 as a, 0 as b) t
+    RexNode divideNode5 = plus(nullInt, div(vIntNotNull(), vIntNotNull()));
+    checkSimplifyUnchanged(divideNode5);
+    // null/(1/0)
+    // ==>
+    // null/(1/0)
+    RexNode divideNode7 = div(nullInt, div(literal(1), literal(0)));
+    checkSimplifyUnchanged(divideNode7);
+    // (1/0)/null
+    // ==>
+    // (1/0)/null
+    RexNode divideNode8 = div(div(literal(1), literal(0)), nullInt);
+    checkSimplifyUnchanged(divideNode8);
+  }
+
+  /** Test cases for IS NULL(x/y).
+   * See <a 
href="https://issues.apache.org/jira/browse/CALCITE-7145";>[CALCITE-7145]
+   * RexSimplify should not simplify IS NULL(10/0)</a>. */
+  @Test void testSimplifyIsNullDivide() {
+    RelDataType intType =
+        typeFactory.createTypeWithNullability(
+            typeFactory.createSqlType(SqlTypeName.INTEGER), false);
+
+    checkSimplifyUnchanged(isNull(div(vIntNotNull(), literal(0))));
+    checkSimplifyUnchanged(isNull(div(vIntNotNull(), cast(literal(0), 
intType))));
+
+    checkSimplifyUnchanged(isNull(div(cast(literal(2), intType), 
vIntNotNull())));
+    checkSimplifyUnchanged(isNull(div(vIntNotNull(), vIntNotNull())));
+
+    checkSimplifyUnchanged(isNotNull(div(vIntNotNull(), literal(0))));
+    checkSimplifyUnchanged(isNotNull(div(vIntNotNull(), cast(literal(0), 
intType))));
+
+    checkSimplifyUnchanged(isNotNull(div(cast(literal(2), intType), 
vIntNotNull())));
+    checkSimplifyUnchanged(isNotNull(div(vIntNotNull(), vIntNotNull())));
+
+    checkSimplifyUnchanged(isNull(div(vDecimalNotNull(), literal(0))));
+
+    checkSimplifyUnchanged(isNotNull(div(vDecimalNotNull(), literal(0))));
   }
 
   @Test void testPushNotIntoCase() {
@@ -2744,9 +2788,9 @@ trueLiteral, literal(1),
         not(
             case_(
                 isTrue(vBool()), vBool(1),
-                gt(div(vIntNotNull(), literal(2)), literal(1)), vBool(2),
+                gt(div(vIntNotNull(), vInt(1)), literal(1)), vBool(2),
                 vBool(3))),
-        "CASE(?0.bool0, NOT(?0.bool1), >(/(?0.notNullInt0, 2), 1), 
NOT(?0.bool2), NOT(?0.bool3))");
+        "CASE(?0.bool0, NOT(?0.bool1), >(/(?0.notNullInt0, ?0.int1), 1), 
NOT(?0.bool2), NOT(?0.bool3))");
   }
 
   @Test void testNotRecursion() {
@@ -4244,7 +4288,9 @@ private SqlSpecialOperatorWithPolicy(String name, SqlKind 
kind, int prec, boolea
     checkSimplify(mul(nullInt, a), "null:INTEGER");
 
     checkSimplify(div(a, one), "?0.notNullInt1");
+    checkSimplify(div(nullInt, one), "null:INTEGER");
     checkSimplify(div(a, nullInt), "null:INTEGER");
+    checkSimplify(div(zero, nullInt), "null:INTEGER");
 
     checkSimplify(add(b, half), "?0.notNullDecimal2");
 
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 fe7b1bfcf9..6b2d1533c6 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -5720,6 +5720,8 @@ private void checkEmptyJoin(RelOptFixture f) {
     final Function<RelBuilder, RelNode> relFn = b -> {
       final RexBuilder rexBuilder = b.getRexBuilder();
       final RelDataType type = 
rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
+      final RelDataType nullableType = rexBuilder.getTypeFactory()
+          .createTypeWithNullability(type, true);
 
       RelNode left = b
           .values(new String[]{"x", "y"}, 1, 2, 2, 1).build();
@@ -5727,19 +5729,22 @@ private void checkEmptyJoin(RelOptFixture f) {
       RexLiteral literal1 = rexBuilder.makeLiteral(1, type);
       RexLiteral literal2 = rexBuilder.makeLiteral(2, type);
       RexLiteral literal3 = rexBuilder.makeLiteral(3, type);
+      // the MOD (%) operation needs to be unsafe to reproduce the scenario
+      RexNode param0 = rexBuilder.makeDynamicParam(nullableType, 0);
+      RexNode param1 = rexBuilder.makeDynamicParam(nullableType, 1);
 
-      // CASE WHEN x % 2 = 1 THEN x < 2
-      //      WHEN x % 3 = 2 THEN x < 1
+      // CASE WHEN x % param0 = 1 THEN x < 2
+      //      WHEN x % param1 = 2 THEN x < 1
       //      ELSE x < 3
       final RexNode caseRexNode =
           rexBuilder.makeCall(
               SqlStdOperatorTable.CASE,
               rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
-                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, literal2),
+                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, param0),
                   literal1),
               rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, 
literal2),
               rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
-                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, literal3),
+                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, param1),
                   literal2),
               rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, 
literal1),
               rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, 
literal3));
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 7268a959a5..651b6ad520 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -15311,7 +15311,7 @@ LogicalProject(QX=[CAST(CASE(=($0, 1), 1, 2)):INTEGER])
   <TestCase name="testReduceCaseWhenWithCast">
     <Resource name="planBefore">
       <![CDATA[
-LogicalProject($f0=[CAST(CASE(=(MOD($0, 2:BIGINT), 1), <($0, 2:BIGINT), 
=(MOD($0, 3:BIGINT), 2), <($0, 1:BIGINT), <($0, 3:BIGINT))):BOOLEAN])
+LogicalProject($f0=[CAST(CASE(=(MOD($0, ?0), 1), <($0, 2:BIGINT), =(MOD($0, 
?1), 2), <($0, 1:BIGINT), <($0, 3:BIGINT))):BOOLEAN])
   LogicalValues(tuples=[[{ 1, 2 }, { 2, 1 }]])
 ]]>
     </Resource>
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java 
b/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
index d9b403a596..eec575e331 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
@@ -33,6 +33,7 @@
 
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.net.URL;
@@ -2902,6 +2903,7 @@ private void testCountWithApproxDistinct(boolean approx, 
String sql,
         .returnsUnordered("EXPR$0=86829");
   }
 
+  @Disabled("CALCITE-7271")
   @Test void testComplexExpressionsIsNull() {
     final String sql = "SELECT COUNT(*) FROM \"foodmart\"  where ( cast(null 
as INTEGER) + cast"
         + "(\"city\" as INTEGER)) IS NULL";
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java 
b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
index f7bf8294dd..87f2c65f4c 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -33,6 +33,7 @@
 
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.net.URL;
@@ -3464,6 +3465,7 @@ private void testCountWithApproxDistinct(boolean approx, 
String sql, String expe
         .returnsUnordered("EXPR$0=86829");
   }
 
+  @Disabled("CALCITE-7271")
   @Test void testComplexExpressionsIsNull() {
     final String sql = "SELECT COUNT(*) FROM \"foodmart\"  where ( cast(null 
as INTEGER) + cast"
         + "(\"city\" as INTEGER)) IS NULL";

Reply via email to