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

xuzifu666 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 eb22ac43ff [CALCITE-6284] Invalid conversion triggers 
ClassCastException
eb22ac43ff is described below

commit eb22ac43ffc4cfc0b2e14d2b71657d5cc71e04e7
Author: Yu Xu <[email protected]>
AuthorDate: Tue Aug 18 17:53:54 2026 +0800

    [CALCITE-6284] Invalid conversion triggers ClassCastException
---
 .../calcite/adapter/enumerable/EnumUtils.java      |  9 +++++
 .../org/apache/calcite/runtime/SqlFunctions.java   | 16 +++++++-
 .../calcite/adapter/enumerable/EnumUtilsTest.java  | 23 ++++++++++++
 .../java/org/apache/calcite/test/JdbcTest.java     | 43 ++++++++++++++++++++++
 4 files changed, 90 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 9cd7ba802b..fd60a29ae7 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -539,6 +539,15 @@ public static Expression convert(Expression operand, Type 
fromType,
         }
       }
     }
+    if (toType == Number.class
+        && (fromType == Object.class || fromType == String.class)) {
+      // E.g. from "Object" to "Number".
+      // Generate "x == null ? null : SqlFunctions.toBigDecimal(x)".
+      return Expressions.condition(
+          Expressions.equal(operand, RexImpTable.NULL_EXPR),
+          RexImpTable.NULL_EXPR,
+          Expressions.call(SqlFunctions.class, "toBigDecimal", operand));
+    }
     if (toPrimitive != null) {
       if (fromPrimitive != null) {
         // E.g. from "float" to "double"
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index d50d7279a5..65a26e05d6 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -5584,7 +5584,18 @@ public static double toDouble(Object o) {
   }
 
   public static BigDecimal toBigDecimal(String s) {
-    return new BigDecimal(s.trim());
+    if (s == null) {
+      throw new NumberFormatException(
+          "Cannot convert null string to BigDecimal");
+    }
+    try {
+      return new BigDecimal(s.trim());
+    } catch (NumberFormatException e) {
+      NumberFormatException ex =
+          new NumberFormatException("Invalid value for BigDecimal: \"" + s + 
"\"");
+      ex.initCause(e);
+      throw ex;
+    }
   }
 
   public static BigDecimal toBigDecimal(Number number) {
@@ -5597,6 +5608,9 @@ public static BigDecimal toBigDecimal(Number number) {
   }
 
   public static BigDecimal toBigDecimal(Object o) {
+    if (o == null) {
+      throw new NumberFormatException("Cannot convert null to BigDecimal");
+    }
     return o instanceof Number ? toBigDecimal((Number) o)
         : toBigDecimal(o.toString());
   }
diff --git 
a/core/src/test/java/org/apache/calcite/adapter/enumerable/EnumUtilsTest.java 
b/core/src/test/java/org/apache/calcite/adapter/enumerable/EnumUtilsTest.java
index 70370d7bb1..d92f50acfa 100644
--- 
a/core/src/test/java/org/apache/calcite/adapter/enumerable/EnumUtilsTest.java
+++ 
b/core/src/test/java/org/apache/calcite/adapter/enumerable/EnumUtilsTest.java
@@ -41,6 +41,29 @@
  */
 public final class EnumUtilsTest {
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6284";>[CALCITE-6284]
+   * Invalid conversion triggers ClassCastException</a>. */
+  @Test void testObjectToNumberConvert() {
+    // Object x;
+    final ParameterExpression objectVariable =
+        Expressions.parameter(0, Object.class, "x");
+    final Expression objectToNumber =
+        EnumUtils.convert(objectVariable, Number.class);
+    assertThat(Expressions.toString(objectToNumber),
+        is("x == null ? (java.math.BigDecimal) null"
+            + " : org.apache.calcite.runtime.SqlFunctions.toBigDecimal(x)"));
+
+    // String s;
+    final ParameterExpression stringVariable =
+        Expressions.parameter(0, String.class, "s");
+    final Expression stringToNumber =
+        EnumUtils.convert(stringVariable, Number.class);
+    assertThat(Expressions.toString(stringToNumber),
+        is("s == null ? (java.math.BigDecimal) null"
+            + " : org.apache.calcite.runtime.SqlFunctions.toBigDecimal(s)"));
+  }
+
   @Test void testDateTypeToInnerTypeConvert() {
     // java.sql.Date x;
     final ParameterExpression date =
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 6dc4a48dc9..a75983c76a 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -9696,6 +9696,49 @@ void checkCalciteSchemaGetSubSchemaMap(boolean cache) {
     }
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6284";>[CALCITE-6284]
+   * Invalid conversion triggers ClassCastException</a>. */
+  @Test void bindStringParameter() {
+    for (SqlTypeName tpe : SqlTypeName.INT_TYPES) {
+      final String sql =
+          "with cte as (select cast(100 as " + tpe.getName() + ") as empid)"
+              + "select * from cte where empid = ?";
+
+      CalciteAssert.hr()
+          .query(sql)
+          .consumesPreparedStatement(p -> {
+            p.setString(1, "100");
+          })
+          .returnsUnordered("EMPID=100");
+    }
+  }
+
+  @Test void bindInvalidStringParameter() {
+    for (SqlTypeName tpe : SqlTypeName.INT_TYPES) {
+      final String sql =
+          "with cte as (select cast(100 as " + tpe.getName() + ") as empid)"
+              + "select * from cte where empid = ?";
+
+      final SQLException e =
+          assertThrows(SQLException.class,
+              () -> CalciteAssert.hr()
+                  .query(sql)
+                  .consumesPreparedStatement(p -> {
+                    p.setString(1, "abc");
+                  })
+                  .returnsUnordered(""));
+      // Should produce a meaningful error, not ClassCastException
+      final Throwable cause = e.getCause();
+      assertThat("Expected NumberFormatException for tpe=" + tpe,
+          cause, instanceOf(NumberFormatException.class));
+      assertThat("Error message should contain the invalid value",
+          cause.getMessage(), containsString("abc"));
+      assertThat("Original NumberFormatException should be preserved as cause",
+          cause.getCause(), instanceOf(NumberFormatException.class));
+    }
+  }
+
   @Test void bindShortParameter() {
     for (SqlTypeName tpe : SqlTypeName.INT_TYPES) {
       final String sql =

Reply via email to