mihaibudiu commented on code in PR #5184:
URL: https://github.com/apache/calcite/pull/5184#discussion_r3797490503


##########
core/src/main/java/org/apache/calcite/rex/RexSimplify.java:
##########
@@ -1197,15 +1197,40 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     if (hasCustomNullabilityRules(a.getKind())) {
       return simplifiedResult;
     }
-    if (!isSafe) {
-      return simplifiedResult;
-    }
     switch (Strong.policy(a)) {
     case NOT_NULL:
+      // Drops the subtree; require full-tree safety so we don't hide runtime
+      // errors inside
+      if (!isSafe) {
+        return simplifiedResult;
+      }
       return rexBuilder.makeLiteral(true);
     case ANY:
       // "f" is a strong operator, so "f(operand0, operand1) IS NOT NULL"
-      // simplifies to "operand0 IS NOT NULL AND operand1 IS NOT NULL"
+      // simplifies to "operand0 IS NOT NULL AND operand1 IS NOT NULL".

Review Comment:
   this comment could be shorter



##########
core/src/main/java/org/apache/calcite/rex/RexSimplify.java:
##########
@@ -1197,15 +1197,40 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     if (hasCustomNullabilityRules(a.getKind())) {
       return simplifiedResult;
     }
-    if (!isSafe) {
-      return simplifiedResult;
-    }
     switch (Strong.policy(a)) {
     case NOT_NULL:
+      // Drops the subtree; require full-tree safety so we don't hide runtime
+      // errors inside
+      if (!isSafe) {
+        return simplifiedResult;
+      }
       return rexBuilder.makeLiteral(true);
     case ANY:
       // "f" is a strong operator, so "f(operand0, operand1) IS NOT NULL"
-      // simplifies to "operand0 IS NOT NULL AND operand1 IS NOT NULL"
+      // simplifies to "operand0 IS NOT NULL AND operand1 IS NOT NULL".
+      // This branch PRESERVES the operand subtrees (each is either recursively
+      // simplified, or rewrapped verbatim as IS NOT NULL(operand)), so it only
+      // needs SHALLOW safety of the outer operator. Requiring full-tree safety
+      // here suppresses common peels like (CAST(str):DOUBLE + 1.0) IS NOT NULL
+      // → CAST(str):DOUBLE IS NOT NULL just because a non-lossless CAST lives
+      // deeper in the tree, even though that CAST would still be evaluated
+      // inside the rewrapped IS NOT NULL(operand).
+      if (!SafeRexVisitor.INSTANCE.isShallowSafe(a)) {
+        return simplifiedResult;
+      }
+      // The peel only preserves runtime semantics when each operand can be

Review Comment:
   and so can this one



##########
core/src/test/java/org/apache/calcite/rex/RexProgramTest.java:
##########
@@ -3024,6 +3024,82 @@ trueLiteral, literal(1),
     checkSimplifyUnchanged(div(cast(vVarchar(), tInt(false)), nullInt));
   }
 
+  /** Test cases for peeling {@code IS [NOT] NULL} across a strong outer

Review Comment:
   Maybe you can follow the established pattern for test case JavaDoc, "Test 
case for [CALCITE-7722]" etc?



##########
core/src/main/java/org/apache/calcite/rex/RexSimplify.java:
##########
@@ -1258,15 +1286,32 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs 
unknownAs) {
     if (hasCustomNullabilityRules(a.getKind())) {
       return simplifiedResult;
     }
-    if (!isSafe) {
-      return simplifiedResult;
-    }
     switch (Strong.policy(a)) {
     case NOT_NULL:
+      // Drops the subtree; require full-tree safety so we don't hide runtime
+      // errors inside
+      if (!isSafe) {
+        return simplifiedResult;
+      }
       return rexBuilder.makeLiteral(false);
     case ANY:
       // "f" is a strong operator, so "f(operand0, operand1) IS NULL" 
simplifies
-      // to "operand0 IS NULL OR operand1 IS NULL"
+      // to "operand0 IS NULL OR operand1 IS NULL". This branch PRESERVES the

Review Comment:
   And I think this one too



##########
core/src/test/java/org/apache/calcite/rex/RexProgramTest.java:
##########
@@ -3024,6 +3024,82 @@ trueLiteral, literal(1),
     checkSimplifyUnchanged(div(cast(vVarchar(), tInt(false)), nullInt));
   }
 
+  /** Test cases for peeling {@code IS [NOT] NULL} across a strong outer
+   * operator whose subtree contains a non-lossless {@code CAST}.
+   *
+   * <p>Distributing {@code IS [NOT] NULL} across a strong operator (e.g.
+   * {@code +}, {@code *}) preserves subtree evaluation: each operand is
+   * either recursively simplified or rewrapped verbatim as
+   * {@code IS [NOT] NULL(operand)}, so the presence of a non-lossless
+   * {@code CAST} deeper in the tree must not block the distribution. */
+  @Test void testSimplifyIsNotNullDistributesAcrossStrongOpWithLossyCast() {
+    // "(CAST(?0.varchar0):INTEGER + 1) IS NOT NULL"
+    //   ==> "IS NOT NULL(CAST(?0.varchar0):INTEGER)"
+    // The outer PLUS is strong AND shallow-safe; distribution keeps the
+    // non-lossless CAST inside the rewrapped IS NOT NULL.
+    checkSimplify(
+        isNotNull(plus(cast(vVarchar(), tInt(true)), literal(1))),
+        "IS NOT NULL(CAST(?0.varchar0):INTEGER)");
+
+    // Symmetric IS NULL peel:
+    // "(CAST(?0.varchar0):INTEGER + 1) IS NULL"
+    //   ==> "IS NULL(CAST(?0.varchar0):INTEGER)"
+    checkSimplify(
+        isNull(plus(cast(vVarchar(), tInt(true)), literal(1))),
+        "IS NULL(CAST(?0.varchar0):INTEGER)");
+
+    // Confirm this is consistent with same expression without CAST
+    checkSimplify(isNotNull(plus(vInt(), literal(1))), "IS NOT NULL(?0.int0)");
+    checkSimplify(isNull(plus(vInt(), literal(1))), "IS NULL(?0.int0)");
+
+    // MULTIPLY is also strong + shallow-safe.
+    checkSimplify(
+        isNotNull(mul(cast(vVarchar(), tInt(true)), literal(2))),
+        "IS NOT NULL(CAST(?0.varchar0):INTEGER)");
+    checkSimplify(
+        isNull(mul(cast(vVarchar(), tInt(true)), literal(2))),
+        "IS NULL(CAST(?0.varchar0):INTEGER)");
+    checkSimplify(isNotNull(mul(vInt(), literal(2))), "IS NOT NULL(?0.int0)");
+    checkSimplify(isNull(mul(vInt(), literal(2))), "IS NULL(?0.int0)");
+
+    // Nested PLUS on both sides: distribution still peels one layer
+    // and stops at the inner CAST, which is not shallow-safe.
+    // "((CAST(?0.varchar0):INTEGER + 1) + 2) IS NOT NULL"
+    //   ==> "IS NOT NULL(CAST(?0.varchar0):INTEGER)"
+    checkSimplify(
+        isNotNull(
+            plus(plus(cast(vVarchar(), tInt(true)), literal(1)), literal(2))),
+        "IS NOT NULL(CAST(?0.varchar0):INTEGER)");
+  }
+
+  /** The distribution must still be suppressed when the outer node is
+   * itself not shallow-safe (e.g. DIVIDE by a literal zero) or when an
+   * operand is typed non-nullable yet not fully safe, otherwise
+   * {@link org.apache.calcite.rex.RexCall#isAlwaysTrue()} would collapse
+   * the rewrapped {@code IS NOT NULL(operand)} to {@code TRUE} and hide
+   * the runtime throw. */
+  @Test void testSimplifyIsNotNullDoesNotDistributeAcrossUnsafeOuter() {
+    // The outer PLUS is shallow-safe, but the div(1, 0) operand is typed
+    // non-nullable, so the peel would rewrap it as IS NOT NULL(/(1, 0))
+    // which the trivial isAlwaysTrue() shortcut would collapse to TRUE
+    // and lose the throw. The peel is therefore suppressed.
+    checkSimplifyUnchanged(isNotNull(plus(div(literal(1), literal(0)), 
vIntNotNull())));
+    checkSimplifyUnchanged(isNull(plus(div(literal(1), literal(0)), 
vIntNotNull())));
+
+    // IS NOT NULL(x/0) itself is not peeled, because DIVIDE with a
+    // literal-zero divisor is not shallow-safe (this branch would
+    // otherwise drop the throwing subexpression).
+    checkSimplifyUnchanged(isNotNull(div(vIntNotNull(), literal(0))));
+    checkSimplifyUnchanged(isNull(div(vIntNotNull(), literal(0))));
+    checkSimplifyUnchanged(isNull(div(cast(vIntNotNull(), tBigInt()), 
literal(0))));
+
+    // IS NULL(CAST(10/0 AS BIGINT)) stays as IS NULL(10/0)
+    // after the lossless-CAST strip; the DIVIDE is not
+    // shallow-safe, so no further distribution occurs.
+    checkSimplify(isNull(cast(div(vIntNotNull(), literal(0)), tBigInt())),

Review Comment:
   can we have some tests using checked arithmetic or arithmetic on intervals?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to