Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 15 additions & 1 deletion core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
43 changes: 43 additions & 0 deletions core/src/test/java/org/apache/calcite/test/JdbcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9695,6 +9695,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() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug report is about the message produced when the conversion FAILS.
What happens if you pass a non-numeric string?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out. The original test only covered the success path. I added bindInvalidStringParameter() and improved the error message:
Before: NumberFormatException with null message.
After: NumberFormatException: Invalid value for BigDecimal: "abc".

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 =
Expand Down
Loading