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 =