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";