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 98fbbb781b9..cf18e7c6f57 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java +++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java @@ -2139,6 +2139,25 @@ private > RexNode simplifyAnd2ForUnknownAsFalse( strongOperands.add(rexFieldAccess); } } + // Also probe sub-expressions coming from IS_NOT_NULL(...) predicates that + // are not bare input refs or field accesses (e.g. CAST calls); without + // this, IS_NOT_NULL(CAST(x)) AND (CAST(x) + 1) < 10 would not be absorbed + for (RexNode operand : notNullOperands) { + if (operand instanceof RexInputRef + || operand instanceof RexFieldAccess + || strongOperands.contains(operand) + || !RexUtil.isDeterministic(operand)) { + continue; + } + final Strong strong = new Strong() { + @Override public boolean isNull(RexNode node) { + return node.equals(operand) || super.isNull(node); + } + }; + if (strong.isNotTrue(term)) { + strongOperands.add(operand); + } + } } // If one column should be null and is in a comparison predicate, // it is not satisfiable. 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 0dbbcd9cdec..2a2efb7521e 100644 --- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java +++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java @@ -30,6 +30,7 @@ import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlSpecialOperator; +import org.apache.calcite.sql.fun.SqlLibraryOperators; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.type.OperandTypes; import org.apache.calcite.sql.type.ReturnTypes; @@ -3025,6 +3026,41 @@ trueLiteral, literal(1), checkSimplifyUnchanged(div(cast(vVarchar(), tInt(false)), nullInt)); } + /** + * Test cases for [CALCITE-7758] + * RexSimplify does not absorb redundant IS NOT NULL on non-input-ref sub-expressions + * in AND simplification. + */ + @Test void testSimplifyIsNotNullAndStrongExpression() { + // "unsafe" cast: (CAST(a) + 1) < 10 AND IS_NOT_NULL(CAST(a)) ==> (CAST(a) + 1) < 10 + checkSimplifyFilter( + and( + lt(plus(cast(vVarchar(), tDouble(true)), literal(1)), literal(10)), + isNotNull(cast(vVarchar(), tDouble(true)))), + "<(+(CAST(?0.varchar0):DOUBLE, 1), 10)"); + + // safe cast: (CAST(a) + 1) < 10 AND IS_NOT_NULL(CAST(a)) ==> (CAST(a) + 1) < 10 + checkSimplifyFilter( + and( + lt(plus(cast(vInt(), tDouble(true)), literal(1)), literal(10)), + isNotNull(cast(vInt(), tDouble(true)))), + "<(+(CAST(?0.int0):DOUBLE, 1), 10)"); + + // op that can return NULL on non-NULL args: + // IS_NOT_NULL(REGEXP_SUBSTR(myField, '[xw]yz')) AND REGEXP_SUBSTR(myField, '[xw]yz') = 'xyz' + // ==> REGEXP_SUBSTR(myField, '[xw]yz') = 'xyz' + checkSimplifyFilter( + and( + isNotNull( + rexBuilder.makeCall( + SqlLibraryOperators.REGEXP_SUBSTR, vVarchar(), literal("[xw]yz"))), + eq( + rexBuilder.makeCall( + SqlLibraryOperators.REGEXP_SUBSTR, vVarchar(), literal("[xw]yz")), + literal("xyz"))), + "=(REGEXP_SUBSTR(?0.varchar0, '[xw]yz'), 'xyz')"); + } + /** * Test cases for [CALCITE-7722] * RexSimplify IS [NOT] NULL on a safe operator with Strong policy ANY and unsafe operands