Skip to content

Commit 47250b1

Browse files
authored
SQL: Add BigDecimal support to JDBC (#56015) (#56220)
* SQL: Add BigDecimal support to JDBC (#56015) * Introduce BigDecimal support to JDBC -- fetching This commit adds support for the getBigDecimal() methods. * Allow BigDecimal params in double range A prepared statement will now accept a BigDecimal parameter as a proxy for a double, if the conversion is lossless. (cherry picked from commit e9a873a) * Fix compilation error Dimond notation with anonymous inner classes not avail in Java8.
1 parent f159fd8 commit 47250b1

File tree

5 files changed

+326
-136
lines changed

5 files changed

+326
-136
lines changed

x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcPreparedStatement.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,13 @@ public void setDouble(int parameterIndex, double x) throws SQLException {
123123

124124
@Override
125125
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
126-
setObject(parameterIndex, x, Types.BIGINT);
126+
// ES lacks proper BigDecimal support, so this function simply maps a BigDecimal to a double, while verifying that no definition
127+
// is lost (i.e. the original value can be conveyed as a double).
128+
// While long (i.e. BIGINT) has a larger scale (than double), double has the higher precision more appropriate for BigDecimal.
129+
if (x.compareTo(BigDecimal.valueOf(x.doubleValue())) != 0) {
130+
throw new SQLException("BigDecimal value [" + x + "] out of supported double's range.");
131+
}
132+
setDouble(parameterIndex, x.doubleValue());
127133
}
128134

129135
@Override

x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcResultSet.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package org.elasticsearch.xpack.sql.jdbc;
77

8+
import org.elasticsearch.common.SuppressForbidden;
9+
810
import java.io.InputStream;
911
import java.io.Reader;
1012
import java.math.BigDecimal;
@@ -300,7 +302,6 @@ private Date asDate(int columnIndex) throws SQLException {
300302
}
301303

302304
try {
303-
304305
return JdbcDateUtils.asDate(val.toString());
305306
} catch (Exception e) {
306307
throw new SQLException(
@@ -525,7 +526,11 @@ public boolean isClosed() {
525526
@Override
526527
@Deprecated
527528
public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
528-
throw new SQLFeatureNotSupportedException("BigDecimal not supported");
529+
BigDecimal bd = getBigDecimal(columnIndex);
530+
// The API doesn't allow for specifying a rounding behavior, although BigDecimals did have a way of controlling rounding, even
531+
// before the API got deprecated => default to fail if scaling can't return an exactly equal value, since this behavior was
532+
// expected by (old) callers as well.
533+
return bd == null ? null : bd.setScale(scale);
529534
}
530535

531536
@Override
@@ -546,8 +551,9 @@ public InputStream getBinaryStream(int columnIndex) throws SQLException {
546551

547552
@Override
548553
@Deprecated
554+
@SuppressForbidden(reason="implementing deprecated method")
549555
public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
550-
throw new SQLFeatureNotSupportedException("BigDecimal not supported");
556+
return getBigDecimal(column(columnLabel), scale);
551557
}
552558

553559
@Override
@@ -594,12 +600,12 @@ public Reader getCharacterStream(String columnLabel) throws SQLException {
594600

595601
@Override
596602
public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
597-
throw new SQLFeatureNotSupportedException("BigDecimal not supported");
603+
return convert(columnIndex, BigDecimal.class);
598604
}
599605

600606
@Override
601607
public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
602-
throw new SQLFeatureNotSupportedException("BigDecimal not supported");
608+
return getBigDecimal(column(columnLabel));
603609
}
604610

605611
@Override

x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/TypeConverter.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.xpack.sql.proto.StringUtils;
1111

1212
import java.io.IOException;
13+
import java.math.BigDecimal;
1314
import java.sql.Date;
1415
import java.sql.SQLException;
1516
import java.sql.SQLFeatureNotSupportedException;
@@ -178,6 +179,9 @@ static <T> T convert(Object val, EsType columnType, Class<T> type, String typeSt
178179
if (type == byte[].class) {
179180
return (T) asByteArray(val, columnType, typeString);
180181
}
182+
if (type == BigDecimal.class) {
183+
return (T) asBigDecimal(val, columnType, typeString);
184+
}
181185
//
182186
// JDK 8 types
183187
//
@@ -537,6 +541,36 @@ private static byte[] asByteArray(Object val, EsType columnType, String typeStri
537541
throw new SQLFeatureNotSupportedException();
538542
}
539543

544+
private static BigDecimal asBigDecimal(Object val, EsType columnType, String typeString) throws SQLException {
545+
switch (columnType) {
546+
case BOOLEAN:
547+
return (Boolean) val ? BigDecimal.ONE : BigDecimal.ZERO;
548+
case BYTE:
549+
case SHORT:
550+
case INTEGER:
551+
case LONG:
552+
return BigDecimal.valueOf(((Number) val).longValue());
553+
case FLOAT:
554+
case HALF_FLOAT:
555+
// floats are passed in as doubles here, so we need to dip into string to keep original float's (reduced) precision.
556+
return new BigDecimal(String.valueOf(((Number) val).floatValue()));
557+
case DOUBLE:
558+
case SCALED_FLOAT:
559+
return BigDecimal.valueOf(((Number) val).doubleValue());
560+
case KEYWORD:
561+
case TEXT:
562+
case CONSTANT_KEYWORD:
563+
try {
564+
return new BigDecimal((String) val);
565+
} catch (NumberFormatException nfe) {
566+
return failConversion(val, columnType, typeString, BigDecimal.class, nfe);
567+
}
568+
// TODO: should we implement numeric - interval types conversions too; ever needed? ODBC does mandate it
569+
// https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types
570+
}
571+
return failConversion(val, columnType, typeString, BigDecimal.class);
572+
}
573+
540574
private static LocalDate asLocalDate(Object val, EsType columnType, String typeString) throws SQLException {
541575
throw new SQLFeatureNotSupportedException();
542576
}

x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/PreparedStatementTestCase.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.elasticsearch.common.collect.Tuple;
99

10+
import java.math.BigDecimal;
1011
import java.sql.Connection;
1112
import java.sql.JDBCType;
1213
import java.sql.ParameterMetaData;
@@ -16,16 +17,12 @@
1617
import java.sql.SQLException;
1718
import java.sql.SQLSyntaxErrorException;
1819

20+
import static org.hamcrest.Matchers.equalTo;
1921
import static org.hamcrest.Matchers.startsWith;
2022

2123
public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
2224

2325
public void testSupportedTypes() throws Exception {
24-
index("library", builder -> {
25-
builder.field("name", "Don Quixote");
26-
builder.field("page_count", 1072);
27-
});
28-
2926
String stringVal = randomAlphaOfLength(randomIntBetween(0, 1000));
3027
int intVal = randomInt();
3128
long longVal = randomLong();
@@ -34,10 +31,11 @@ public void testSupportedTypes() throws Exception {
3431
boolean booleanVal = randomBoolean();
3532
byte byteVal = randomByte();
3633
short shortVal = randomShort();
34+
BigDecimal bigDecimalVal = BigDecimal.valueOf(randomDouble());
3735

3836
try (Connection connection = esJdbc()) {
3937
try (PreparedStatement statement = connection.prepareStatement(
40-
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, name FROM library WHERE page_count=?")) {
38+
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?")) {
4139
statement.setString(1, stringVal);
4240
statement.setInt(2, intVal);
4341
statement.setLong(3, longVal);
@@ -47,7 +45,7 @@ public void testSupportedTypes() throws Exception {
4745
statement.setBoolean(7, booleanVal);
4846
statement.setByte(8, byteVal);
4947
statement.setShort(9, shortVal);
50-
statement.setInt(10, 1072);
48+
statement.setBigDecimal(10, bigDecimalVal);
5149

5250
try (ResultSet results = statement.executeQuery()) {
5351
ResultSetMetaData resultSetMetaData = results.getMetaData();
@@ -67,13 +65,23 @@ public void testSupportedTypes() throws Exception {
6765
assertEquals(booleanVal, results.getBoolean(7));
6866
assertEquals(byteVal, results.getByte(8));
6967
assertEquals(shortVal, results.getShort(9));
70-
assertEquals("Don Quixote", results.getString(10));
68+
assertEquals(bigDecimalVal, results.getBigDecimal(10));
7169
assertFalse(results.next());
7270
}
7371
}
7472
}
7573
}
7674

75+
public void testOutOfRangeBigDecimal() throws Exception {
76+
try (Connection connection = esJdbc()) {
77+
try (PreparedStatement statement = connection.prepareStatement("SELECT ?")) {
78+
BigDecimal tooLarge = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.ONE);
79+
SQLException ex = expectThrows(SQLException.class, () -> statement.setBigDecimal(1, tooLarge));
80+
assertThat(ex.getMessage(), equalTo("BigDecimal value [" + tooLarge + "] out of supported double's range."));
81+
}
82+
}
83+
}
84+
7785
public void testUnsupportedParameterUse() throws Exception {
7886
index("library", builder -> {
7987
builder.field("name", "Don Quixote");

0 commit comments

Comments
 (0)