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

chunwei 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 4ee98e0c06 [CALCITE-4861] Optimization of chained CAST calls can lead 
to unexpected behavior
4ee98e0c06 is described below

commit 4ee98e0c0625e5adc7b2eed3e60702ee744c435d
Author: Benchao Li <[email protected]>
AuthorDate: Sun Apr 17 15:24:18 2022 +0800

    [CALCITE-4861] Optimization of chained CAST calls can lead to unexpected 
behavior
---
 .../java/org/apache/calcite/rex/RexBuilder.java    | 23 +++++++
 .../calcite/test/TypeCoercionConverterTest.xml     |  2 +-
 .../org/apache/calcite/test/SqlOperatorTest.java   | 76 ++++++++++++----------
 3 files changed, 67 insertions(+), 34 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java 
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 6a6e599dbc..b4d8e66f1d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -642,6 +642,9 @@ public class RexBuilder {
 
   boolean canRemoveCastFromLiteral(RelDataType toType, @Nullable Comparable 
value,
       SqlTypeName fromTypeName) {
+    if (value == null) {
+      return true;
+    }
     final SqlTypeName sqlType = toType.getSqlTypeName();
     if (!RexLiteral.valueMatchesType(value, sqlType, false)) {
       return false;
@@ -678,6 +681,26 @@ public class RexBuilder {
       return SqlTypeUtil.isValidDecimalValue(decimalValue, toType);
     }
 
+    if (SqlTypeName.INT_TYPES.contains(sqlType)) {
+      final BigDecimal decimalValue = (BigDecimal) value;
+      final int s = decimalValue.scale();
+      if (s != 0) {
+        return false;
+      }
+      long l = decimalValue.longValue();
+      switch (sqlType) {
+      case TINYINT:
+        return l >= Byte.MIN_VALUE && l <= Byte.MAX_VALUE;
+      case SMALLINT:
+        return l >= Short.MIN_VALUE && l <= Short.MAX_VALUE;
+      case INTEGER:
+        return l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE;
+      case BIGINT:
+      default:
+        return true;
+      }
+    }
+
     return true;
   }
 
diff --git 
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
index 9aed98d285..7e3fc66395 100644
--- 
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
+++ 
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
@@ -171,7 +171,7 @@ LogicalUnion(all=[false])
     </Resource>
     <Resource name="plan">
       <![CDATA[
-LogicalTableModify(table=[[CATALOG, SALES, T1]], operation=[UPDATE], 
updateColumnList=[[t1_varchar20, t1_date, t1_int]], 
sourceExpressionList=[[CAST(123):VARCHAR(20) NOT NULL, CAST(2020-01-03 
10:14:34):DATE NOT NULL, 12.3]], flattened=[false])
+LogicalTableModify(table=[[CATALOG, SALES, T1]], operation=[UPDATE], 
updateColumnList=[[t1_varchar20, t1_date, t1_int]], 
sourceExpressionList=[[CAST(123):VARCHAR(20) NOT NULL, CAST(2020-01-03 
10:14:34):DATE NOT NULL, CAST(12.3:DECIMAL(3, 1)):INTEGER NOT NULL]], 
flattened=[false])
   LogicalProject(t1_varchar20=[$0], t1_smallint=[$1], t1_int=[$2], 
t1_bigint=[$3], t1_float=[$4], t1_double=[$5], t1_decimal=[$6], 
t1_timestamp=[$7], t1_date=[$8], t1_binary=[$9], t1_boolean=[$10], 
EXPR$0=[123], EXPR$1=[2020-01-03 10:14:34], EXPR$2=[12.3:DECIMAL(3, 1)])
     LogicalTableScan(table=[[CATALOG, SALES, T1]])
 ]]>
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 538b6349d6..a2337fd985 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -805,36 +805,36 @@ public class SqlOperatorTest {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.CAST, VmName.EXPAND);
 
-    f.checkCastToScalarOkay("1.25", "INTEGER", "1");
-    f.checkCastToScalarOkay("1.25E0", "INTEGER", "1");
+    f.checkFails("cast(1.25 as int)", "INTEGER", true);
+    f.checkFails("cast(1.25E0 as int)", "INTEGER", true);
     if (!f.brokenTestsEnabled()) {
       return;
     }
-    f.checkCastToScalarOkay("1.5", "INTEGER", "2");
-    f.checkCastToScalarOkay("5E-1", "INTEGER", "1");
-    f.checkCastToScalarOkay("1.75", "INTEGER", "2");
-    f.checkCastToScalarOkay("1.75E0", "INTEGER", "2");
-
-    f.checkCastToScalarOkay("-1.25", "INTEGER", "-1");
-    f.checkCastToScalarOkay("-1.25E0", "INTEGER", "-1");
-    f.checkCastToScalarOkay("-1.5", "INTEGER", "-2");
-    f.checkCastToScalarOkay("-5E-1", "INTEGER", "-1");
-    f.checkCastToScalarOkay("-1.75", "INTEGER", "-2");
-    f.checkCastToScalarOkay("-1.75E0", "INTEGER", "-2");
-
-    f.checkCastToScalarOkay("1.23454", "DECIMAL(8, 4)", "1.2345");
-    f.checkCastToScalarOkay("1.23454E0", "DECIMAL(8, 4)", "1.2345");
-    f.checkCastToScalarOkay("1.23455", "DECIMAL(8, 4)", "1.2346");
-    f.checkCastToScalarOkay("5E-5", "DECIMAL(8, 4)", "0.0001");
-    f.checkCastToScalarOkay("1.99995", "DECIMAL(8, 4)", "2.0000");
-    f.checkCastToScalarOkay("1.99995E0", "DECIMAL(8, 4)", "2.0000");
-
-    f.checkCastToScalarOkay("-1.23454", "DECIMAL(8, 4)", "-1.2345");
-    f.checkCastToScalarOkay("-1.23454E0", "DECIMAL(8, 4)", "-1.2345");
-    f.checkCastToScalarOkay("-1.23455", "DECIMAL(8, 4)", "-1.2346");
-    f.checkCastToScalarOkay("-5E-5", "DECIMAL(8, 4)", "-0.0001");
-    f.checkCastToScalarOkay("-1.99995", "DECIMAL(8, 4)", "-2.0000");
-    f.checkCastToScalarOkay("-1.99995E0", "DECIMAL(8, 4)", "-2.0000");
+    f.checkFails("cast(1.5 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(5E-1 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.75 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.75E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+
+    f.checkFails("cast(-1.25 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.25E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.5 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-5E-1 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.75 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.75E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+
+    f.checkFails("cast(1.23454 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.23454E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.23455 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(5E-5 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.99995 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(1.99995E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+
+    f.checkFails("cast(-1.23454 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.23454E0 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.23455 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-5E-5 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.99995 as int)", OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast(-1.99995E0 as int)", OUT_OF_RANGE_MESSAGE, true);
 
     // 9.99 round to 10.0, should give out of range error
     f.checkFails("cast(9.99 as decimal(2,1))", OUT_OF_RANGE_MESSAGE,
@@ -845,15 +845,15 @@ public class SqlOperatorTest {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.CAST, VmName.EXPAND);
 
-    f.checkScalarExact("cast( cast(1.25 as double) as integer)", 1);
-    f.checkScalarExact("cast( cast(-1.25 as double) as integer)", -1);
+    f.checkFails("cast( cast(1.25 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast( cast(-1.25 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
     if (!f.brokenTestsEnabled()) {
       return;
     }
-    f.checkScalarExact("cast( cast(1.75 as double) as integer)", 2);
-    f.checkScalarExact("cast( cast(-1.75 as double) as integer)", -2);
-    f.checkScalarExact("cast( cast(1.5 as double) as integer)", 2);
-    f.checkScalarExact("cast( cast(-1.5 as double) as integer)", -2);
+    f.checkFails("cast( cast(1.75 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast( cast(-1.75 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast( cast(1.5 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
+    f.checkFails("cast( cast(-1.5 as double) as integer)", 
OUT_OF_RANGE_MESSAGE, true);
   }
 
   @Test void testCastApproxNumericLimits() {
@@ -1237,6 +1237,16 @@ public class SqlOperatorTest {
         INVALID_CHAR_MESSAGE, true);
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4861";>[CALCITE-4861]
+   * Optimisation of chained cast calls can lead to unexpected behaviour.</a>.
+   */
+  @Test void testChainedCast() {
+    final SqlOperatorFixture f = fixture();
+    f.checkFails("CAST(CAST(CAST(123456 AS TINYINT) AS INT) AS BIGINT)",
+        "Value out of range. Value:\"123456\"", true);
+  }
+
   @Test void testCase() {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.CASE, VmName.EXPAND);

Reply via email to