Skip to content

Commit 306229e

Browse files
committed
Added checks of invalid values of Decimal
1 parent 1a39b84 commit 306229e

File tree

6 files changed

+102
-43
lines changed

6 files changed

+102
-43
lines changed

jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public final class YdbConst {
103103
public static final String UNABLE_TO_CONVERT = "Cannot cast [%s] with value [%s] to [%s]";
104104
public static final String UNABLE_TO_CONVERT_AS_URL = "Cannot cast as URL: ";
105105
public static final String UNABLE_TO_CAST_TO_CLASS = "Cannot cast [%s] to class [%s]";
106+
public static final String UNABLE_TO_CAST_TO_DECIMAL = "Cannot cast to decimal type %s: [%s] is %s";
106107

107108
public static final String MISSING_VALUE_FOR_PARAMETER = "Missing value for parameter: ";
108109
public static final String MISSING_REQUIRED_VALUE = "Missing required value for parameter: ";

jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,6 @@
3030
import tech.ydb.table.values.Type;
3131
import tech.ydb.table.values.Value;
3232

33-
import static tech.ydb.table.values.PrimitiveType.Bool;
34-
import static tech.ydb.table.values.PrimitiveType.Bytes;
35-
import static tech.ydb.table.values.PrimitiveType.Date;
36-
import static tech.ydb.table.values.PrimitiveType.Datetime;
37-
import static tech.ydb.table.values.PrimitiveType.Double;
38-
import static tech.ydb.table.values.PrimitiveType.Float;
39-
import static tech.ydb.table.values.PrimitiveType.Int16;
40-
import static tech.ydb.table.values.PrimitiveType.Int32;
41-
import static tech.ydb.table.values.PrimitiveType.Int64;
42-
import static tech.ydb.table.values.PrimitiveType.Int8;
43-
import static tech.ydb.table.values.PrimitiveType.Interval;
44-
import static tech.ydb.table.values.PrimitiveType.Json;
45-
import static tech.ydb.table.values.PrimitiveType.JsonDocument;
46-
import static tech.ydb.table.values.PrimitiveType.Text;
47-
import static tech.ydb.table.values.PrimitiveType.Timestamp;
48-
import static tech.ydb.table.values.PrimitiveType.TzDate;
49-
import static tech.ydb.table.values.PrimitiveType.TzDatetime;
50-
import static tech.ydb.table.values.PrimitiveType.TzTimestamp;
51-
import static tech.ydb.table.values.PrimitiveType.Uint16;
52-
import static tech.ydb.table.values.PrimitiveType.Uint32;
53-
import static tech.ydb.table.values.PrimitiveType.Uint64;
54-
import static tech.ydb.table.values.PrimitiveType.Uint8;
55-
import static tech.ydb.table.values.PrimitiveType.Uuid;
56-
import static tech.ydb.table.values.PrimitiveType.Yson;
57-
import static tech.ydb.table.values.Type.Kind.PRIMITIVE;
58-
5933
public class MappingGetters {
6034
private MappingGetters() { }
6135

@@ -95,7 +69,7 @@ static Getters buildGetters(Type type) {
9569
value -> value.getDecimal().toBigDecimal().floatValue(),
9670
value -> value.getDecimal().toBigDecimal().doubleValue(),
9771
castToBytesNotSupported(clazz),
98-
PrimitiveReader::getDecimal,
72+
value -> value.getDecimal().toBigDecimal(),
9973
castToClassNotSupported(clazz),
10074
castToInstantNotSupported(clazz),
10175
castToNStringNotSupported(clazz),

jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -466,23 +466,36 @@ private static PrimitiveValue castToTimestamp(PrimitiveType type, Object x) thro
466466
throw castNotSupported(type, x);
467467
}
468468

469+
private static DecimalValue validateValue(DecimalType type, DecimalValue value, Object x) throws SQLException {
470+
if (value.isInf()) {
471+
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "Infinite"));
472+
}
473+
if (value.isNegativeInf()) {
474+
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "-Infinite"));
475+
}
476+
if (value.isNan()) {
477+
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "NaN"));
478+
}
479+
return value;
480+
}
481+
469482
private static DecimalValue castToDecimalValue(DecimalType type, Object x) throws SQLException {
470483
if (x instanceof DecimalValue) {
471-
return (DecimalValue) x;
484+
return validateValue(type, (DecimalValue) x, x);
472485
} else if (x instanceof BigDecimal) {
473-
return type.newValue((BigDecimal) x);
486+
return validateValue(type, type.newValue((BigDecimal) x), x);
474487
} else if (x instanceof BigInteger) {
475-
return type.newValue((BigInteger) x);
488+
return validateValue(type, type.newValue((BigInteger) x), x);
476489
} else if (x instanceof Long) {
477-
return type.newValue((Long) x);
490+
return validateValue(type, type.newValue((Long) x), x);
478491
} else if (x instanceof Integer) {
479-
return type.newValue((Integer) x);
492+
return validateValue(type, type.newValue((Integer) x), x);
480493
} else if (x instanceof Short) {
481-
return type.newValue((Short) x);
494+
return validateValue(type, type.newValue((Short) x), x);
482495
} else if (x instanceof Byte) {
483-
return type.newValue((Byte) x);
496+
return validateValue(type, type.newValue((Byte) x), x);
484497
} else if (x instanceof String) {
485-
return type.newValue((String) x);
498+
return validateValue(type, type.newValue((String) x), x);
486499
}
487500
throw castNotSupported(type.getKind(), x);
488501
}

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbLazyResultSetImplTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,7 +1636,7 @@ public void getObject() throws SQLException {
16361636
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(311111156, 0, ZoneOffset.UTC))
16371637
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(311111, 223342000))
16381638
.typedValue(21, "c_Interval", Duration.parse("PT3.111113S"))
1639-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("3.335000000"));
1639+
.typedValue(22, "c_Decimal", new BigDecimal("3.335000000"));
16401640

16411641
checker.nextRow()
16421642
.typedValue(1, "key", 2)
@@ -1660,7 +1660,7 @@ public void getObject() throws SQLException {
16601660
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(211211100, 0, ZoneOffset.UTC))
16611661
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(111111, 223342000))
16621662
.typedValue(21, "c_Interval", Duration.parse("PT3.112113S"))
1663-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("-3.335000000"));
1663+
.typedValue(22, "c_Decimal", new BigDecimal("-3.335000000"));
16641664

16651665
checker.nextRow()
16661666
.typedValue(1, "key", 3)
@@ -1684,7 +1684,7 @@ public void getObject() throws SQLException {
16841684
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC))
16851685
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(0, 0))
16861686
.typedValue(21, "c_Interval", Duration.parse("PT0.000000S"))
1687-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("0.00000000"));
1687+
.typedValue(22, "c_Decimal", new BigDecimal("0.000000000"));
16881688

16891689
checker.nextRow()
16901690
.typedValue(1, "key", 4)
@@ -1708,7 +1708,7 @@ public void getObject() throws SQLException {
17081708
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(1, 0, ZoneOffset.UTC))
17091709
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(0, 1000))
17101710
.typedValue(21, "c_Interval", Duration.parse("PT0.000001S"))
1711-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("1.00000000"));
1711+
.typedValue(22, "c_Decimal", new BigDecimal("1.000000000"));
17121712

17131713
checker.nextRow()
17141714
.value(1, "key", 5)

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tech.ydb.jdbc.impl;
22

33
import java.math.BigDecimal;
4+
import java.math.BigInteger;
45
import java.sql.Date;
56
import java.sql.PreparedStatement;
67
import java.sql.ResultSet;
@@ -982,4 +983,74 @@ private void assertNextDate(ResultSet rs, int key, LocalDate ld) throws SQLExcep
982983
Assertions.assertEquals(ld.atStartOfDay(), rs.getObject("c_Date", LocalDateTime.class));
983984
Assertions.assertEquals(ld.atStartOfDay(ZoneId.systemDefault()).toInstant(), rs.getObject("c_Date", Instant.class));
984985
}
986+
987+
@ParameterizedTest(name = "with {0}")
988+
@EnumSource(SqlQueries.JdbcQuery.class)
989+
public void decimalTest(SqlQueries.JdbcQuery query) throws SQLException {
990+
String upsert = TEST_TABLE.upsertOne(query, "c_Decimal", "Decimal(22, 9)");
991+
992+
// YDB partially ignores Decimal(22, 9) limit, but have hard limit to 35 digits
993+
String maxValue = "9999999999" + "9999999999" + "9999999999" + "99999";
994+
BigDecimal closeToInf = new BigDecimal(new BigInteger(maxValue), 9);
995+
BigDecimal closeToNegInf = new BigDecimal(new BigInteger(maxValue).negate(), 9);
996+
try (PreparedStatement ps = jdbc.connection().prepareStatement(upsert)) {
997+
ps.setInt(1, 1);
998+
ps.setBigDecimal(2, BigDecimal.valueOf(1.5d));
999+
ps.execute();
1000+
1001+
ps.setInt(1, 2);
1002+
ps.setBigDecimal(2, BigDecimal.valueOf(-12345, 10)); // will be rounded to -0.000001234
1003+
ps.execute();
1004+
1005+
ps.setInt(1, 3);
1006+
ps.setBigDecimal(2, closeToInf);
1007+
ps.execute();
1008+
1009+
ps.setInt(1, 4);
1010+
ps.setBigDecimal(2, closeToNegInf);
1011+
ps.execute();
1012+
1013+
ps.setInt(1, 5);
1014+
ExceptionAssert.sqlException(""
1015+
+ "Cannot cast to decimal type Decimal(22, 9): "
1016+
+ "[class java.math.BigDecimal: 100000000000000000000000000.000000000] is Infinite",
1017+
() -> ps.setBigDecimal(2, closeToInf.add(BigDecimal.valueOf(1, 9)))
1018+
);
1019+
1020+
ExceptionAssert.sqlException(""
1021+
+ "Cannot cast to decimal type Decimal(22, 9): "
1022+
+ "[class java.math.BigDecimal: -100000000000000000000000000.000000000] is -Infinite",
1023+
() -> ps.setBigDecimal(2, closeToNegInf.subtract(BigDecimal.valueOf(1, 9)))
1024+
);
1025+
1026+
ExceptionAssert.sqlException(""
1027+
+ "Cannot cast to decimal type Decimal(22, 9): "
1028+
+ "[class java.math.BigDecimal: 100000000000000000000000000.000000001] is NaN",
1029+
() -> ps.setBigDecimal(2, closeToInf.add(BigDecimal.valueOf(2, 9)))
1030+
);
1031+
}
1032+
1033+
try (Statement st = jdbc.connection().createStatement()) {
1034+
try (ResultSet rs = st.executeQuery(TEST_TABLE.selectColumn("c_Decimal"))) {
1035+
assertNextDecimal(rs, 1, BigDecimal.valueOf(1.5d).setScale(9));
1036+
assertNextDecimal(rs, 2, BigDecimal.valueOf(-1234, 9));
1037+
assertNextDecimal(rs, 3, closeToInf);
1038+
assertNextDecimal(rs, 4, closeToNegInf);
1039+
1040+
Assertions.assertFalse(rs.next());
1041+
}
1042+
}
1043+
};
1044+
1045+
private void assertNextDecimal(ResultSet rs, int key, BigDecimal bg) throws SQLException {
1046+
Assertions.assertTrue(rs.next());
1047+
Assertions.assertEquals(key, rs.getInt("key"));
1048+
1049+
Object obj = rs.getObject("c_Decimal");
1050+
Assertions.assertTrue(obj instanceof BigDecimal);
1051+
Assertions.assertEquals(bg, obj);
1052+
1053+
BigDecimal decimal = rs.getBigDecimal("c_Decimal");
1054+
Assertions.assertEquals(bg, decimal);
1055+
}
9851056
}

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbResultSetImplTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,7 +1797,7 @@ public void getObject() throws SQLException {
17971797
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(311111156, 0, ZoneOffset.UTC))
17981798
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(311111, 223342000))
17991799
.typedValue(21, "c_Interval", Duration.parse("PT3.111113S"))
1800-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("3.335000000"));
1800+
.typedValue(22, "c_Decimal", new BigDecimal("3.335000000"));
18011801

18021802
checker.nextRow()
18031803
.typedValue(1, "key", 2)
@@ -1821,7 +1821,7 @@ public void getObject() throws SQLException {
18211821
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(211211100, 0, ZoneOffset.UTC))
18221822
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(111111, 223342000))
18231823
.typedValue(21, "c_Interval", Duration.parse("PT3.112113S"))
1824-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("-3.335000000"));
1824+
.typedValue(22, "c_Decimal", new BigDecimal("-3.335000000"));
18251825

18261826
checker.nextRow()
18271827
.typedValue(1, "key", 3)
@@ -1845,7 +1845,7 @@ public void getObject() throws SQLException {
18451845
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC))
18461846
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(0, 0))
18471847
.typedValue(21, "c_Interval", Duration.parse("PT0.000000S"))
1848-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("0.00000000"));
1848+
.typedValue(22, "c_Decimal", new BigDecimal("0.000000000"));
18491849

18501850
checker.nextRow()
18511851
.typedValue(1, "key", 4)
@@ -1869,7 +1869,7 @@ public void getObject() throws SQLException {
18691869
.typedValue(19, "c_Datetime", LocalDateTime.ofEpochSecond(1, 0, ZoneOffset.UTC))
18701870
.typedValue(20, "c_Timestamp", Instant.ofEpochSecond(0, 1000))
18711871
.typedValue(21, "c_Interval", Duration.parse("PT0.000001S"))
1872-
.typedValue(22, "c_Decimal", DecimalType.getDefault().newValue("1.00000000"));
1872+
.typedValue(22, "c_Decimal", new BigDecimal("1.000000000"));
18731873

18741874
checker.nextRow()
18751875
.value(1, "key", 5)

0 commit comments

Comments
 (0)