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 51965cfbcb [CALCITE-7295] RexSimplify should simplify a division with
a NULL argument
51965cfbcb is described below
commit 51965cfbcba638ed9335e5d3cc7b560003d5f85c
Author: Thomas Rebele <[email protected]>
AuthorDate: Mon Nov 17 19:07:46 2025 +0100
[CALCITE-7295] RexSimplify should simplify a division with a NULL argument
---
.../java/org/apache/calcite/rex/RexSimplify.java | 34 ++++++----
.../org/apache/calcite/rex/RexProgramTest.java | 74 +++++++++++++---------
2 files changed, 66 insertions(+), 42 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 2ad299675f..0704731137 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -418,17 +418,22 @@ private RexNode simplifyGenericNode(RexCall e) {
*/
private static int findLiteralIndex(List<RexNode> operands, BigDecimal
value) {
for (int i = 0; i < operands.size(); i++) {
- if (operands.get(i).isA(SqlKind.LITERAL)) {
- Comparable comparable = ((RexLiteral) operands.get(i)).getValue();
- if (comparable instanceof BigDecimal
- && value.compareTo((BigDecimal) comparable) == 0) {
- return i;
- }
+ if (checkLiteralValue(operands.get(i), value)) {
+ return i;
}
}
return -1;
}
+ /** Check whether the operand is a BigDecimal literal of the specified
value. */
+ private static boolean checkLiteralValue(RexNode operand, BigDecimal value) {
+ if (!operand.isA(SqlKind.LITERAL)) {
+ return false;
+ }
+ Comparable<?> comparable = ((RexLiteral) operand).getValue();
+ return comparable instanceof BigDecimal && value.compareTo((BigDecimal)
comparable) == 0;
+ }
+
private RexNode simplifyArithmetic(RexCall e) {
if (e.getType().getSqlTypeName().getFamily() != SqlTypeFamily.NUMERIC
|| e.getOperands().stream().anyMatch(
@@ -491,8 +496,8 @@ private RexNode simplifyMultiply(RexCall e) {
}
private RexNode simplifyDivide(RexCall e) {
- final int oneIndex = findLiteralIndex(e.operands, BigDecimal.ONE);
- if (oneIndex == 1) {
+ RexNode rightOperand = e.getOperands().get(1);
+ if (checkLiteralValue(rightOperand, BigDecimal.ONE)) {
RexNode leftOperand = e.getOperands().get(0);
return leftOperand.getType().equals(e.getType())
? leftOperand : rexBuilder.makeCast(e.getParserPosition(),
e.getType(), leftOperand);
@@ -1561,14 +1566,19 @@ enum SafeRexVisitor implements RexVisitor<Boolean> {
case DIVIDE:
case MOD:
List<RexNode> operands = call.getOperands();
- boolean isSafe = RexVisitorImpl.visitArrayAnd(this,
ImmutableList.of(operands.get(0)));
- if (!isSafe) {
+ boolean areOperandsSafe = RexVisitorImpl.visitArrayAnd(this,
call.operands);
+ if (!areOperandsSafe) {
return false;
}
+ boolean hasNullOperand = RexUtil.isNullLiteral(operands.get(0), true)
+ || RexUtil.isNullLiteral(operands.get(1), true);
+ if (hasNullOperand) {
+ return true;
+ }
if (operands.get(1) instanceof RexLiteral) {
- RexLiteral literal = (RexLiteral) operands.get(1);
- return RexUtil.isNullLiteral(literal, true);
+ return !checkLiteralValue(operands.get(1), BigDecimal.ZERO);
}
+ // the safety of division could not be deduced, so assume it is unsafe
return false;
default:
break;
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 91f55624e8..fc769f966c 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -2712,48 +2712,37 @@ trueLiteral, literal(1),
* <a
href="https://issues.apache.org/jira/browse/CALCITE-7032">[CALCITE-7032]
* Simplify 'NULL > ALL (ARRAY[1,2,NULL])' to 'NULL'</a>. */
@Test void testSimplifyDivideSafe() {
- // null + (a/0)/4
- // ==>
- // null + (a/0)/4
+ // 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
- // ==>
- // null + a/4
+ // null + a/4 ==> null
RexNode divideNode1 = plus(nullInt, div(vIntNotNull(), literal(4)));
- checkSimplifyUnchanged(divideNode1);
- // null + a/null
- // ==>
- // null
+ checkSimplify(divideNode1, "null:INTEGER");
+ // null + a/null ==> null
RexNode divideNode2 = plus(nullInt, div(vIntNotNull(), nullInt));
checkSimplify(divideNode2, "null:INTEGER");
- // null + null/0
- // ==>
- // null + null/0
+ // null + null/0 ==> null
+ // The SQL standard gives NULL a higher priority than division by 0.
+ // This is the same behavior as PostgreSQL, Oracle, SQLite, MariaDB, and
MySQL.
RexNode divideNode3 = plus(nullInt, div(nullInt, literal(0)));
- checkSimplifyUnchanged(divideNode3);
- // null + a/0
- // ==>
- // null + a/0
+ checkSimplify(divideNode3, "null:INTEGER");
+ // null + a/0 ==> null + a/0
RexNode divideNode4 = plus(nullInt, div(vIntNotNull(), literal(0)));
checkSimplifyUnchanged(divideNode4);
- // null + a/b
- // ==>
- // null + a/b
+ // null + a/b ==> null + a/b
// 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)
+ // (1/0) + (null/0) ==> (1/0) + null
+ RexNode divideNode6 = plus(div(literal(1), literal(0)), div(nullInt,
literal(0)));
+ checkSimplify(divideNode6, "+(/(1, 0), null)");
+ // null/(1/0) ==> null/(1/0)
RexNode divideNode7 = div(nullInt, div(literal(1), literal(0)));
checkSimplifyUnchanged(divideNode7);
- // (1/0)/null
- // ==>
- // (1/0)/null
+ // (1/0)/null ==> (1/0)/null
RexNode divideNode8 = div(div(literal(1), literal(0)), nullInt);
checkSimplifyUnchanged(divideNode8);
}
@@ -2762,25 +2751,50 @@ trueLiteral, literal(1),
* 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);
+ RelDataType intType = tInt(false);
checkSimplifyUnchanged(isNull(div(vIntNotNull(), literal(0))));
checkSimplifyUnchanged(isNull(div(vIntNotNull(), cast(literal(0),
intType))));
+ checkSimplify(isNull(div(vIntNotNull(), cast(literal(2), intType))),
"false");
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))));
+ checkSimplify(isNotNull(div(vIntNotNull(), cast(literal(2), intType))),
"true");
checkSimplifyUnchanged(isNotNull(div(cast(literal(2), intType),
vIntNotNull())));
checkSimplifyUnchanged(isNotNull(div(vIntNotNull(), vIntNotNull())));
checkSimplifyUnchanged(isNull(div(vDecimalNotNull(), literal(0))));
+ checkSimplify(
+ isNull(div(vDecimalNotNull(), cast(literal(BigDecimal.valueOf(2.5)),
intType))),
+ "false");
checkSimplifyUnchanged(isNotNull(div(vDecimalNotNull(), literal(0))));
+ checkSimplify(
+ isNotNull(div(vDecimalNotNull(),
cast(literal(BigDecimal.valueOf(2.5)), intType))),
+ "true");
+ }
+
+ /**
+ * Test cases for <a
href="https://issues.apache.org/jira/browse/CALCITE-7295">[CALCITE-7295]
+ * RexSimplify should simplify a division with a NULL argument</a>.
+ */
+ @Test void testSimplifyIsNullDivideWithNullArgument() {
+ checkSimplify(isNull(div(nullInt, literal(0))), "true");
+ checkSimplify(isNull(div(literal(0), nullInt)), "true");
+
+ checkSimplify(isNotNull(div(nullInt, literal(0))), "false");
+ checkSimplify(isNotNull(div(literal(0), nullInt)), "false");
+
+ checkSimplify(isNull(div(nullDecimal, literal(BigDecimal.ZERO))), "true");
+ checkSimplify(isNotNull(div(nullDecimal, literal(BigDecimal.ZERO))),
"false");
+
+ // do not simplify if one of the operands may throw an exception
+ checkSimplifyUnchanged(div(nullInt, cast(vVarchar(), tInt(false))));
+ checkSimplifyUnchanged(div(cast(vVarchar(), tInt(false)), nullInt));
}
@Test void testPushNotIntoCase() {
@@ -2996,7 +3010,6 @@ private SqlOperator getNoDeterministicOperator() {
checkSimplifyUnchanged(isNotNull(cast(vVarchar(), tVarbinary(true))));
}
-
@Test void checkSimplifyDynamicParam() {
checkSimplify(isNotNull(lt(vInt(0), vInt(1))),
"AND(IS NOT NULL(?0.int0), IS NOT NULL(?0.int1))");
@@ -4291,6 +4304,7 @@ private SqlSpecialOperatorWithPolicy(String name, SqlKind
kind, int prec, boolea
checkSimplify(div(nullInt, one), "null:INTEGER");
checkSimplify(div(a, nullInt), "null:INTEGER");
checkSimplify(div(zero, nullInt), "null:INTEGER");
+ checkSimplify(div(nullInt, zero), "null:INTEGER");
checkSimplify(add(b, half), "?0.notNullDecimal2");