diff --git a/jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java b/jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java index b8d6c1b..c44fd83 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java @@ -103,6 +103,7 @@ public final class YdbConst { public static final String UNABLE_TO_CONVERT = "Cannot cast [%s] with value [%s] to [%s]"; public static final String UNABLE_TO_CONVERT_AS_URL = "Cannot cast as URL: "; public static final String UNABLE_TO_CAST_TO_CLASS = "Cannot cast [%s] to class [%s]"; + public static final String UNABLE_TO_CAST_TO_DECIMAL = "Cannot cast to decimal type %s: [%s] is %s"; public static final String MISSING_VALUE_FOR_PARAMETER = "Missing value for parameter: "; public static final String MISSING_REQUIRED_VALUE = "Missing required value for parameter: "; diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java index e0b166a..9f2ba02 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java @@ -30,32 +30,6 @@ import tech.ydb.table.values.Type; import tech.ydb.table.values.Value; -import static tech.ydb.table.values.PrimitiveType.Bool; -import static tech.ydb.table.values.PrimitiveType.Bytes; -import static tech.ydb.table.values.PrimitiveType.Date; -import static tech.ydb.table.values.PrimitiveType.Datetime; -import static tech.ydb.table.values.PrimitiveType.Double; -import static tech.ydb.table.values.PrimitiveType.Float; -import static tech.ydb.table.values.PrimitiveType.Int16; -import static tech.ydb.table.values.PrimitiveType.Int32; -import static tech.ydb.table.values.PrimitiveType.Int64; -import static tech.ydb.table.values.PrimitiveType.Int8; -import static tech.ydb.table.values.PrimitiveType.Interval; -import static tech.ydb.table.values.PrimitiveType.Json; -import static tech.ydb.table.values.PrimitiveType.JsonDocument; -import static tech.ydb.table.values.PrimitiveType.Text; -import static tech.ydb.table.values.PrimitiveType.Timestamp; -import static tech.ydb.table.values.PrimitiveType.TzDate; -import static tech.ydb.table.values.PrimitiveType.TzDatetime; -import static tech.ydb.table.values.PrimitiveType.TzTimestamp; -import static tech.ydb.table.values.PrimitiveType.Uint16; -import static tech.ydb.table.values.PrimitiveType.Uint32; -import static tech.ydb.table.values.PrimitiveType.Uint64; -import static tech.ydb.table.values.PrimitiveType.Uint8; -import static tech.ydb.table.values.PrimitiveType.Uuid; -import static tech.ydb.table.values.PrimitiveType.Yson; -import static tech.ydb.table.values.Type.Kind.PRIMITIVE; - public class MappingGetters { private MappingGetters() { } @@ -95,7 +69,7 @@ static Getters buildGetters(Type type) { value -> value.getDecimal().toBigDecimal().floatValue(), value -> value.getDecimal().toBigDecimal().doubleValue(), castToBytesNotSupported(clazz), - PrimitiveReader::getDecimal, + value -> value.getDecimal().toBigDecimal(), castToClassNotSupported(clazz), castToInstantNotSupported(clazz), castToNStringNotSupported(clazz), diff --git a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java index 750006e..a068ea5 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java @@ -466,23 +466,36 @@ private static PrimitiveValue castToTimestamp(PrimitiveType type, Object x) thro throw castNotSupported(type, x); } + private static DecimalValue validateValue(DecimalType type, DecimalValue value, Object x) throws SQLException { + if (value.isInf()) { + throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "Infinite")); + } + if (value.isNegativeInf()) { + throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "-Infinite")); + } + if (value.isNan()) { + throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "NaN")); + } + return value; + } + private static DecimalValue castToDecimalValue(DecimalType type, Object x) throws SQLException { if (x instanceof DecimalValue) { - return (DecimalValue) x; + return validateValue(type, (DecimalValue) x, x); } else if (x instanceof BigDecimal) { - return type.newValue((BigDecimal) x); + return validateValue(type, type.newValue((BigDecimal) x), x); } else if (x instanceof BigInteger) { - return type.newValue((BigInteger) x); + return validateValue(type, type.newValue((BigInteger) x), x); } else if (x instanceof Long) { - return type.newValue((Long) x); + return validateValue(type, type.newValue((Long) x), x); } else if (x instanceof Integer) { - return type.newValue((Integer) x); + return validateValue(type, type.newValue((Integer) x), x); } else if (x instanceof Short) { - return type.newValue((Short) x); + return validateValue(type, type.newValue((Short) x), x); } else if (x instanceof Byte) { - return type.newValue((Byte) x); + return validateValue(type, type.newValue((Byte) x), x); } else if (x instanceof String) { - return type.newValue((String) x); + return validateValue(type, type.newValue((String) x), x); } throw castNotSupported(type.getKind(), x); } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java index 724048b..6fe3239 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java @@ -1,6 +1,7 @@ package tech.ydb.jdbc.impl; import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -982,4 +983,74 @@ private void assertNextDate(ResultSet rs, int key, LocalDate ld) throws SQLExcep Assertions.assertEquals(ld.atStartOfDay(), rs.getObject("c_Date", LocalDateTime.class)); Assertions.assertEquals(ld.atStartOfDay(ZoneId.systemDefault()).toInstant(), rs.getObject("c_Date", Instant.class)); } + + @ParameterizedTest(name = "with {0}") + @EnumSource(SqlQueries.JdbcQuery.class) + public void decimalTest(SqlQueries.JdbcQuery query) throws SQLException { + String upsert = TEST_TABLE.upsertOne(query, "c_Decimal", "Decimal(22, 9)"); + + // YDB partially ignores Decimal(22, 9) limit, but have hard limit to 35 digits + String maxValue = "9999999999" + "9999999999" + "9999999999" + "99999"; + BigDecimal closeToInf = new BigDecimal(new BigInteger(maxValue), 9); + BigDecimal closeToNegInf = new BigDecimal(new BigInteger(maxValue).negate(), 9); + try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert)) { + ps.setInt(1, 1); + ps.setBigDecimal(2, BigDecimal.valueOf(1.5d)); + ps.execute(); + + ps.setInt(1, 2); + ps.setBigDecimal(2, BigDecimal.valueOf(-12345, 10)); // will be rounded to -0.000001234 + ps.execute(); + + ps.setInt(1, 3); + ps.setBigDecimal(2, closeToInf); + ps.execute(); + + ps.setInt(1, 4); + ps.setBigDecimal(2, closeToNegInf); + ps.execute(); + + ps.setInt(1, 5); + ExceptionAssert.sqlException("" + + "Cannot cast to decimal type Decimal(22, 9): " + + "[class java.math.BigDecimal: 100000000000000000000000000.000000000] is Infinite", + () -> ps.setBigDecimal(2, closeToInf.add(BigDecimal.valueOf(1, 9))) + ); + + ExceptionAssert.sqlException("" + + "Cannot cast to decimal type Decimal(22, 9): " + + "[class java.math.BigDecimal: -100000000000000000000000000.000000000] is -Infinite", + () -> ps.setBigDecimal(2, closeToNegInf.subtract(BigDecimal.valueOf(1, 9))) + ); + + ExceptionAssert.sqlException("" + + "Cannot cast to decimal type Decimal(22, 9): " + + "[class java.math.BigDecimal: 100000000000000000000000000.000000001] is NaN", + () -> ps.setBigDecimal(2, closeToInf.add(BigDecimal.valueOf(2, 9))) + ); + } + + try (Statement st = jdbc.connection().createStatement()) { + try (ResultSet rs = st.executeQuery(TEST_TABLE.selectColumn("c_Decimal"))) { + assertNextDecimal(rs, 1, BigDecimal.valueOf(1.5d).setScale(9)); + assertNextDecimal(rs, 2, BigDecimal.valueOf(-1234, 9)); + assertNextDecimal(rs, 3, closeToInf); + assertNextDecimal(rs, 4, closeToNegInf); + + Assertions.assertFalse(rs.next()); + } + } + }; + + private void assertNextDecimal(ResultSet rs, int key, BigDecimal bg) throws SQLException { + Assertions.assertTrue(rs.next()); + Assertions.assertEquals(key, rs.getInt("key")); + + Object obj = rs.getObject("c_Decimal"); + Assertions.assertTrue(obj instanceof BigDecimal); + Assertions.assertEquals(bg, obj); + + BigDecimal decimal = rs.getBigDecimal("c_Decimal"); + Assertions.assertEquals(bg, decimal); + } }