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 9cd7ba802bd..fd60a29ae75 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 b797264a8bb..4e2f0af448f 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -5522,7 +5522,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) {
@@ -5535,6 +5546,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 70370d7bb1a..d92f50acfae 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
+ * [CALCITE-6284]
+ * Invalid conversion triggers ClassCastException. */
+ @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 d235fa327c7..c74d0d39b83 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -9695,6 +9695,49 @@ void checkCalciteSchemaGetSubSchemaMap(boolean cache) {
}
}
+ /** Test case for
+ * [CALCITE-6284]
+ * Invalid conversion triggers ClassCastException. */
+ @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 =