JDBC driver prepared statement set* methods (#31494)

Added setObject functionality and tests for it
This commit is contained in:
Andrei Stefan 2018-06-27 10:05:39 +03:00 committed by GitHub
parent a35b5341c4
commit 400db4f37d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 837 additions and 49 deletions

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.xpack.sql.type.DataType;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
@ -21,13 +23,24 @@ import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
final PreparedQuery query;
@ -74,67 +87,67 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
setParam(parameterIndex, x, Types.BOOLEAN);
setObject(parameterIndex, x, Types.BOOLEAN);
}
@Override
public void setByte(int parameterIndex, byte x) throws SQLException {
setParam(parameterIndex, x, Types.TINYINT);
setObject(parameterIndex, x, Types.TINYINT);
}
@Override
public void setShort(int parameterIndex, short x) throws SQLException {
setParam(parameterIndex, x, Types.SMALLINT);
setObject(parameterIndex, x, Types.SMALLINT);
}
@Override
public void setInt(int parameterIndex, int x) throws SQLException {
setParam(parameterIndex, x, Types.INTEGER);
setObject(parameterIndex, x, Types.INTEGER);
}
@Override
public void setLong(int parameterIndex, long x) throws SQLException {
setParam(parameterIndex, x, Types.BIGINT);
setObject(parameterIndex, x, Types.BIGINT);
}
@Override
public void setFloat(int parameterIndex, float x) throws SQLException {
setParam(parameterIndex, x, Types.REAL);
setObject(parameterIndex, x, Types.REAL);
}
@Override
public void setDouble(int parameterIndex, double x) throws SQLException {
setParam(parameterIndex, x, Types.DOUBLE);
setObject(parameterIndex, x, Types.DOUBLE);
}
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
throw new SQLFeatureNotSupportedException("BigDecimal not supported");
setObject(parameterIndex, x, Types.BIGINT);
}
@Override
public void setString(int parameterIndex, String x) throws SQLException {
setParam(parameterIndex, x, Types.VARCHAR);
setObject(parameterIndex, x, Types.VARCHAR);
}
@Override
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
throw new UnsupportedOperationException("Bytes not implemented yet");
setObject(parameterIndex, x, Types.VARBINARY);
}
@Override
public void setDate(int parameterIndex, Date x) throws SQLException {
throw new UnsupportedOperationException("Date/Time not implemented yet");
setObject(parameterIndex, x, Types.TIMESTAMP);
}
@Override
public void setTime(int parameterIndex, Time x) throws SQLException {
throw new UnsupportedOperationException("Date/Time not implemented yet");
setObject(parameterIndex, x, Types.TIMESTAMP);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
throw new UnsupportedOperationException("Date/Time not implemented yet");
setObject(parameterIndex, x, Types.TIMESTAMP);
}
@Override
@ -161,12 +174,22 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
throw new UnsupportedOperationException("Object not implemented yet");
// the value of scaleOrLength parameter doesn't matter, as it's not used in the called method below
setObject(parameterIndex, x, targetSqlType, 0);
}
@Override
public void setObject(int parameterIndex, Object x) throws SQLException {
throw new SQLFeatureNotSupportedException("CharacterStream not supported");
if (x == null) {
setParam(parameterIndex, null, Types.NULL);
return;
}
// check also here the unsupported types so that any unsupported interfaces ({@code java.sql.Struct},
// {@code java.sql.Array} etc) will generate the correct exception message. Otherwise, the method call
// {@code TypeConverter.fromJavaToJDBC(x.getClass())} will report the implementing class as not being supported.
checkKnownUnsupportedTypes(x);
setObject(parameterIndex, x, TypeConverter.fromJavaToJDBC(x.getClass()).getVendorTypeNumber(), 0);
}
@Override
@ -181,22 +204,22 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setRef(int parameterIndex, Ref x) throws SQLException {
throw new SQLFeatureNotSupportedException("Ref not supported");
setObject(parameterIndex, x);
}
@Override
public void setBlob(int parameterIndex, Blob x) throws SQLException {
throw new SQLFeatureNotSupportedException("Blob not supported");
setObject(parameterIndex, x);
}
@Override
public void setClob(int parameterIndex, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException("Clob not supported");
setObject(parameterIndex, x);
}
@Override
public void setArray(int parameterIndex, Array x) throws SQLException {
throw new SQLFeatureNotSupportedException("Array not supported");
setObject(parameterIndex, x);
}
@Override
@ -206,17 +229,44 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
throw new UnsupportedOperationException("Dates not implemented yet");
if (cal == null) {
setObject(parameterIndex, x, Types.TIMESTAMP);
return;
}
if (x == null) {
setNull(parameterIndex, Types.TIMESTAMP);
return;
}
// converting to UTC since this is what ES is storing internally
setObject(parameterIndex, new Date(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal)), Types.TIMESTAMP);
}
@Override
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
throw new UnsupportedOperationException("Dates not implemented yet");
if (cal == null) {
setObject(parameterIndex, x, Types.TIMESTAMP);
return;
}
if (x == null) {
setNull(parameterIndex, Types.TIMESTAMP);
return;
}
// converting to UTC since this is what ES is storing internally
setObject(parameterIndex, new Time(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal)), Types.TIMESTAMP);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
throw new UnsupportedOperationException("Dates not implemented yet");
if (cal == null) {
setObject(parameterIndex, x, Types.TIMESTAMP);
return;
}
if (x == null) {
setNull(parameterIndex, Types.TIMESTAMP);
return;
}
// converting to UTC since this is what ES is storing internally
setObject(parameterIndex, new Timestamp(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal)), Types.TIMESTAMP);
}
@Override
@ -226,7 +276,7 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setURL(int parameterIndex, URL x) throws SQLException {
throw new SQLFeatureNotSupportedException("Datalink not supported");
setObject(parameterIndex, x);
}
@Override
@ -236,7 +286,7 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setRowId(int parameterIndex, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException("RowId not supported");
setObject(parameterIndex, x);
}
@Override
@ -251,7 +301,7 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setNClob(int parameterIndex, NClob value) throws SQLException {
throw new SQLFeatureNotSupportedException("NClob not supported");
setObject(parameterIndex, value);
}
@Override
@ -271,12 +321,108 @@ class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
@Override
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException("SQLXML not supported");
setObject(parameterIndex, xmlObject);
}
@Override
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
throw new UnsupportedOperationException("Object not implemented yet");
checkOpen();
JDBCType targetJDBCType;
try {
// this is also a way to check early for the validity of the desired sql type
targetJDBCType = JDBCType.valueOf(targetSqlType);
} catch (IllegalArgumentException e) {
throw new SQLDataException(e.getMessage());
}
// set the null value on the type and exit
if (x == null) {
setParam(parameterIndex, null, targetSqlType);
return;
}
checkKnownUnsupportedTypes(x);
if (x instanceof byte[]) {
if (targetJDBCType != JDBCType.VARBINARY) {
throw new SQLFeatureNotSupportedException(
"Conversion from type byte[] to " + targetJDBCType + " not supported");
}
setParam(parameterIndex, x, Types.VARBINARY);
return;
}
if (x instanceof Timestamp
|| x instanceof Calendar
|| x instanceof Date
|| x instanceof LocalDateTime
|| x instanceof Time
|| x instanceof java.util.Date)
{
if (targetJDBCType == JDBCType.TIMESTAMP) {
// converting to {@code java.util.Date} because this is the type supported by {@code XContentBuilder} for serialization
java.util.Date dateToSet;
if (x instanceof Timestamp) {
dateToSet = new java.util.Date(((Timestamp) x).getTime());
} else if (x instanceof Calendar) {
dateToSet = ((Calendar) x).getTime();
} else if (x instanceof Date) {
dateToSet = new java.util.Date(((Date) x).getTime());
} else if (x instanceof LocalDateTime){
LocalDateTime ldt = (LocalDateTime) x;
Calendar cal = getDefaultCalendar();
cal.set(ldt.getYear(), ldt.getMonthValue() - 1, ldt.getDayOfMonth(), ldt.getHour(), ldt.getMinute(), ldt.getSecond());
dateToSet = cal.getTime();
} else if (x instanceof Time) {
dateToSet = new java.util.Date(((Time) x).getTime());
} else {
dateToSet = (java.util.Date) x;
}
setParam(parameterIndex, dateToSet, Types.TIMESTAMP);
return;
} else if (targetJDBCType == JDBCType.VARCHAR) {
setParam(parameterIndex, String.valueOf(x), Types.VARCHAR);
return;
}
// anything else other than VARCHAR and TIMESTAMP is not supported in this JDBC driver
throw new SQLFeatureNotSupportedException(
"Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported");
}
if (x instanceof Boolean
|| x instanceof Byte
|| x instanceof Short
|| x instanceof Integer
|| x instanceof Long
|| x instanceof Float
|| x instanceof Double
|| x instanceof String) {
setParam(parameterIndex,
TypeConverter.convert(x, TypeConverter.fromJavaToJDBC(x.getClass()), DataType.fromJdbcTypeToJava(targetJDBCType)),
targetSqlType);
return;
}
throw new SQLFeatureNotSupportedException(
"Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported");
}
private void checkKnownUnsupportedTypes(Object x) throws SQLFeatureNotSupportedException {
List<Class<?>> unsupportedTypes = new ArrayList<Class<?>>(Arrays.asList(Struct.class, Array.class, SQLXML.class,
RowId.class, Ref.class, Blob.class, NClob.class, Clob.class, LocalDate.class, LocalTime.class,
OffsetTime.class, OffsetDateTime.class, URL.class, BigDecimal.class));
for (Class<?> clazz:unsupportedTypes) {
if (clazz.isAssignableFrom(x.getClass())) {
throw new SQLFeatureNotSupportedException("Objects of type " + clazz.getName() + " are not supported");
}
}
}
private Calendar getDefaultCalendar() {
return Calendar.getInstance(cfg.timeZone(), Locale.ROOT);
}
@Override

View File

@ -359,14 +359,6 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
return null;
}
if (type != null && type.isInstance(val)) {
try {
return type.cast(val);
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to " + type, cce);
}
}
JDBCType columnType = cursor.columns().get(columnIndex - 1).type;
return TypeConverter.convert(val, columnType, type);

View File

@ -10,7 +10,9 @@ import org.elasticsearch.xpack.sql.type.DataType;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
@ -18,10 +20,17 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static java.util.Calendar.DAY_OF_MONTH;
@ -48,6 +57,22 @@ final class TypeConverter {
}
private static final long DAY_IN_MILLIS = 60 * 60 * 24;
private static final Map<Class<?>, JDBCType> javaToJDBC;
static {
Map<Class<?>, JDBCType> aMap = Arrays.stream(DataType.values())
.filter(dataType -> dataType.javaClass() != null
&& dataType != DataType.HALF_FLOAT
&& dataType != DataType.SCALED_FLOAT
&& dataType != DataType.TEXT)
.collect(Collectors.toMap(dataType -> dataType.javaClass(), dataType -> dataType.jdbcType));
// apart from the mappings in {@code DataType} three more Java classes can be mapped to a {@code JDBCType.TIMESTAMP}
// according to B-4 table from the jdbc4.2 spec
aMap.put(Calendar.class, JDBCType.TIMESTAMP);
aMap.put(java.util.Date.class, JDBCType.TIMESTAMP);
aMap.put(LocalDateTime.class, JDBCType.TIMESTAMP);
javaToJDBC = Collections.unmodifiableMap(aMap);
}
/**
* Converts millisecond after epoc to date
@ -94,6 +119,20 @@ final class TypeConverter {
c.setTimeInMillis(initial);
}
}
static long convertFromCalendarToUTC(long value, Calendar cal) {
if (cal == null) {
return value;
}
Calendar c = (Calendar) cal.clone();
c.setTimeInMillis(value);
ZonedDateTime convertedDateTime = ZonedDateTime
.ofInstant(c.toInstant(), c.getTimeZone().toZoneId())
.withZoneSameLocal(ZoneOffset.UTC);
return convertedDateTime.toInstant().toEpochMilli();
}
/**
* Converts object val from columnType to type
@ -103,6 +142,15 @@ final class TypeConverter {
if (type == null) {
return (T) convert(val, columnType);
}
if (type.isInstance(val)) {
try {
return type.cast(val);
} catch (ClassCastException cce) {
throw new SQLDataException("Unable to convert " + val.getClass().getName() + " to " + columnType, cce);
}
}
if (type == String.class) {
return (T) asString(convert(val, columnType));
}
@ -174,10 +222,10 @@ final class TypeConverter {
// Convert unsupported exception to JdbcSQLException
throw new JdbcSQLException(ex, ex.getMessage());
}
if (dataType.javaName == null) {
if (dataType.javaClass() == null) {
throw new JdbcSQLException("Unsupported JDBC type [" + jdbcType + "]");
}
return dataType.javaName;
return dataType.javaClass().getName();
}
/**
@ -228,6 +276,18 @@ final class TypeConverter {
}
return dataType.isSigned();
}
static JDBCType fromJavaToJDBC(Class<?> clazz) throws SQLException {
for (Entry<Class<?>, JDBCType> e : javaToJDBC.entrySet()) {
// java.util.Calendar from {@code javaToJDBC} is an abstract class and this method can be used with concrete classes as well
if (e.getKey().isAssignableFrom(clazz)) {
return e.getValue();
}
}
throw new SQLFeatureNotSupportedException("Objects of type " + clazz.getName() + " are not supported");
}
private static Double doubleValue(Object v) {
if (v instanceof String) {
@ -275,7 +335,7 @@ final class TypeConverter {
case REAL:
case FLOAT:
case DOUBLE:
return Boolean.valueOf(Integer.signum(((Number) val).intValue()) == 0);
return Boolean.valueOf(Integer.signum(((Number) val).intValue()) != 0);
default:
throw new SQLException("Conversion from type [" + columnType + "] to [Boolean] not supported");
@ -454,28 +514,28 @@ final class TypeConverter {
private static byte safeToByte(long x) throws SQLException {
if (x > Byte.MAX_VALUE || x < Byte.MIN_VALUE) {
throw new SQLException(format(Locale.ROOT, "Numeric %d out of range", Long.toString(x)));
throw new SQLException(format(Locale.ROOT, "Numeric %s out of range", Long.toString(x)));
}
return (byte) x;
}
private static short safeToShort(long x) throws SQLException {
if (x > Short.MAX_VALUE || x < Short.MIN_VALUE) {
throw new SQLException(format(Locale.ROOT, "Numeric %d out of range", Long.toString(x)));
throw new SQLException(format(Locale.ROOT, "Numeric %s out of range", Long.toString(x)));
}
return (short) x;
}
private static int safeToInt(long x) throws SQLException {
if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE) {
throw new SQLException(format(Locale.ROOT, "Numeric %d out of range", Long.toString(x)));
throw new SQLException(format(Locale.ROOT, "Numeric %s out of range", Long.toString(x)));
}
return (int) x;
}
private static long safeToLong(double x) throws SQLException {
if (x > Long.MAX_VALUE || x < Long.MIN_VALUE) {
throw new SQLException(format(Locale.ROOT, "Numeric %d out of range", Double.toString(x)));
throw new SQLException(format(Locale.ROOT, "Numeric %s out of range", Double.toString(x)));
}
return Math.round(x);
}

View File

@ -0,0 +1,582 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.test.ESTestCase;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.JDBCType;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import static java.sql.JDBCType.BIGINT;
import static java.sql.JDBCType.BOOLEAN;
import static java.sql.JDBCType.DOUBLE;
import static java.sql.JDBCType.FLOAT;
import static java.sql.JDBCType.INTEGER;
import static java.sql.JDBCType.REAL;
import static java.sql.JDBCType.SMALLINT;
import static java.sql.JDBCType.TIMESTAMP;
import static java.sql.JDBCType.TINYINT;
import static java.sql.JDBCType.VARBINARY;
import static java.sql.JDBCType.VARCHAR;
public class JdbcPreparedStatementTests extends ESTestCase {
public void testSettingBooleanValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
jps.setBoolean(1, true);
assertEquals(true, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
jps.setObject(1, false);
assertEquals(false, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
jps.setObject(1, true, Types.BOOLEAN);
assertEquals(true, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
assertTrue(value(jps) instanceof Boolean);
jps.setObject(1, true, Types.INTEGER);
assertEquals(1, value(jps));
assertEquals(INTEGER, jdbcType(jps));
jps.setObject(1, true, Types.VARCHAR);
assertEquals("true", value(jps));
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingBooleanValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, true, Types.TIMESTAMP));
assertEquals("Conversion from type [BOOLEAN] to [Timestamp] not supported", sqle.getMessage());
}
public void testSettingStringValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
jps.setString(1, "foo bar");
assertEquals("foo bar", value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, "foo bar");
assertEquals("foo bar", value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, "foo bar", Types.VARCHAR);
assertEquals("foo bar", value(jps));
assertEquals(VARCHAR, jdbcType(jps));
assertTrue(value(jps) instanceof String);
}
public void testThrownExceptionsWhenSettingStringValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, "foo bar", Types.INTEGER));
assertEquals("Conversion from type [VARCHAR] to [Integer] not supported", sqle.getMessage());
}
public void testSettingByteTypeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
jps.setByte(1, (byte) 6);
assertEquals((byte) 6, value(jps));
assertEquals(TINYINT, jdbcType(jps));
jps.setObject(1, (byte) 6);
assertEquals((byte) 6, value(jps));
assertEquals(TINYINT, jdbcType(jps));
assertTrue(value(jps) instanceof Byte);
jps.setObject(1, (byte) 0, Types.BOOLEAN);
assertEquals(false, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
jps.setObject(1, (byte) 123, Types.BOOLEAN);
assertEquals(true, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
jps.setObject(1, (byte) 123, Types.INTEGER);
assertEquals(123, value(jps));
assertEquals(INTEGER, jdbcType(jps));
jps.setObject(1, (byte) -128, Types.DOUBLE);
assertEquals(-128.0, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingByteTypeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (byte) 6, Types.TIMESTAMP));
assertEquals("Conversion from type [TINYINT] to [Timestamp] not supported", sqle.getMessage());
}
public void testSettingShortTypeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
short someShort = randomShort();
jps.setShort(1, someShort);
assertEquals(someShort, value(jps));
assertEquals(SMALLINT, jdbcType(jps));
jps.setObject(1, someShort);
assertEquals(someShort, value(jps));
assertEquals(SMALLINT, jdbcType(jps));
assertTrue(value(jps) instanceof Short);
jps.setObject(1, (short) 1, Types.BOOLEAN);
assertEquals(true, value(jps));
assertEquals(BOOLEAN, jdbcType(jps));
jps.setObject(1, (short) -32700, Types.DOUBLE);
assertEquals(-32700.0, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
jps.setObject(1, someShort, Types.INTEGER);
assertEquals((int) someShort, value(jps));
assertEquals(INTEGER, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingShortTypeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (short) 6, Types.TIMESTAMP));
assertEquals("Conversion from type [SMALLINT] to [Timestamp] not supported", sqle.getMessage());
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, 256, Types.TINYINT));
assertEquals("Numeric " + 256 + " out of range", sqle.getMessage());
}
public void testSettingIntegerValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
int someInt = randomInt();
jps.setInt(1, someInt);
assertEquals(someInt, value(jps));
assertEquals(INTEGER, jdbcType(jps));
jps.setObject(1, someInt);
assertEquals(someInt, value(jps));
assertEquals(INTEGER, jdbcType(jps));
assertTrue(value(jps) instanceof Integer);
jps.setObject(1, someInt, Types.VARCHAR);
assertEquals(String.valueOf(someInt), value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, someInt, Types.FLOAT);
assertEquals(Double.valueOf(someInt), value(jps));
assertTrue(value(jps) instanceof Double);
assertEquals(FLOAT, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingIntegerValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
int someInt = randomInt();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someInt, Types.TIMESTAMP));
assertEquals("Conversion from type [INTEGER] to [Timestamp] not supported", sqle.getMessage());
Integer randomIntNotShort = randomIntBetween(32768, Integer.MAX_VALUE);
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomIntNotShort, Types.SMALLINT));
assertEquals("Numeric " + randomIntNotShort + " out of range", sqle.getMessage());
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomIntNotShort, Types.TINYINT));
assertEquals("Numeric " + randomIntNotShort + " out of range", sqle.getMessage());
}
public void testSettingLongValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
long someLong = randomLong();
jps.setLong(1, someLong);
assertEquals(someLong, value(jps));
assertEquals(BIGINT, jdbcType(jps));
jps.setObject(1, someLong);
assertEquals(someLong, value(jps));
assertEquals(BIGINT, jdbcType(jps));
assertTrue(value(jps) instanceof Long);
jps.setObject(1, someLong, Types.VARCHAR);
assertEquals(String.valueOf(someLong), value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, someLong, Types.DOUBLE);
assertEquals((double) someLong, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
jps.setObject(1, someLong, Types.FLOAT);
assertEquals((double) someLong, value(jps));
assertEquals(FLOAT, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingLongValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
long someLong = randomLong();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someLong, Types.TIMESTAMP));
assertEquals("Conversion from type [BIGINT] to [Timestamp] not supported", sqle.getMessage());
Long randomLongNotShort = randomLongBetween(Integer.MAX_VALUE + 1, Long.MAX_VALUE);
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomLongNotShort, Types.INTEGER));
assertEquals("Numeric " + randomLongNotShort + " out of range", sqle.getMessage());
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomLongNotShort, Types.SMALLINT));
assertEquals("Numeric " + randomLongNotShort + " out of range", sqle.getMessage());
}
public void testSettingFloatValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
float someFloat = randomFloat();
jps.setFloat(1, someFloat);
assertEquals(someFloat, value(jps));
assertEquals(REAL, jdbcType(jps));
jps.setObject(1, someFloat);
assertEquals(someFloat, value(jps));
assertEquals(REAL, jdbcType(jps));
assertTrue(value(jps) instanceof Float);
jps.setObject(1, someFloat, Types.VARCHAR);
assertEquals(String.valueOf(someFloat), value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, someFloat, Types.DOUBLE);
assertEquals((double) someFloat, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
jps.setObject(1, someFloat, Types.FLOAT);
assertEquals((double) someFloat, value(jps));
assertEquals(FLOAT, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingFloatValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
float someFloat = randomFloat();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someFloat, Types.TIMESTAMP));
assertEquals("Conversion from type [REAL] to [Timestamp] not supported", sqle.getMessage());
Float floatNotInt = 5_155_000_000f;
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, floatNotInt, Types.INTEGER));
assertEquals(String.format(Locale.ROOT, "Numeric %s out of range",
Long.toString(Math.round(floatNotInt.doubleValue()))), sqle.getMessage());
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, floatNotInt, Types.SMALLINT));
assertEquals(String.format(Locale.ROOT, "Numeric %s out of range",
Long.toString(Math.round(floatNotInt.doubleValue()))), sqle.getMessage());
}
public void testSettingDoubleValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
double someDouble = randomDouble();
jps.setDouble(1, someDouble);
assertEquals(someDouble, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
jps.setObject(1, someDouble);
assertEquals(someDouble, value(jps));
assertEquals(DOUBLE, jdbcType(jps));
assertTrue(value(jps) instanceof Double);
jps.setObject(1, someDouble, Types.VARCHAR);
assertEquals(String.valueOf(someDouble), value(jps));
assertEquals(VARCHAR, jdbcType(jps));
jps.setObject(1, someDouble, Types.REAL);
assertEquals(new Float(someDouble), value(jps));
assertEquals(REAL, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingDoubleValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
double someDouble = randomDouble();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someDouble, Types.TIMESTAMP));
assertEquals("Conversion from type [DOUBLE] to [Timestamp] not supported", sqle.getMessage());
Double doubleNotInt = 5_155_000_000d;
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, doubleNotInt, Types.INTEGER));
assertEquals(String.format(Locale.ROOT, "Numeric %s out of range",
Long.toString(((Number) doubleNotInt).longValue())), sqle.getMessage());
}
public void testUnsupportedClasses() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLFeatureNotSupportedException sfnse = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, new Struct() {
@Override
public String getSQLTypeName() throws SQLException {
return null;
}
@Override
public Object[] getAttributes(Map<String, Class<?>> map) throws SQLException {
return null;
}
@Override
public Object[] getAttributes() throws SQLException {
return null;
}
}));
assertEquals("Objects of type java.sql.Struct are not supported", sfnse.getMessage());
sfnse = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, new URL("http://test")));
assertEquals("Objects of type java.net.URL are not supported", sfnse.getMessage());
sfnse = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setURL(1, new URL("http://test")));
assertEquals("Objects of type java.net.URL are not supported", sfnse.getMessage());
sfnse = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, this, Types.TIMESTAMP));
assertEquals("Conversion from type " + this.getClass().getName() + " to TIMESTAMP not supported", sfnse.getMessage());
SQLException se = expectThrows(SQLException.class, () -> jps.setObject(1, this, 1_000_000));
assertEquals("Type:1000000 is not a valid Types.java value.", se.getMessage());
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> jps.setObject(1, randomShort(), Types.CHAR));
assertEquals("Unsupported JDBC type [CHAR]", iae.getMessage());
}
public void testSettingTimestampValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch());
jps.setTimestamp(1, someTimestamp);
assertEquals(someTimestamp.getTime(), ((Date)value(jps)).getTime());
assertEquals(TIMESTAMP, jdbcType(jps));
Calendar nonDefaultCal = randomCalendar();
// February 29th, 2016. 01:17:55 GMT = 1456708675000 millis since epoch
jps.setTimestamp(1, new Timestamp(1456708675000L), nonDefaultCal);
assertEquals(1456708675000L, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertEquals(TIMESTAMP, jdbcType(jps));
long beforeEpochTime = -randomMillisSinceEpoch();
jps.setTimestamp(1, new Timestamp(beforeEpochTime), nonDefaultCal);
assertEquals(beforeEpochTime, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, someTimestamp, Types.VARCHAR);
assertEquals(someTimestamp.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingTimestampValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someTimestamp, Types.INTEGER));
assertEquals("Conversion from type java.sql.Timestamp to INTEGER not supported", sqle.getMessage());
}
public void testSettingTimeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Time time = new Time(4675000);
Calendar nonDefaultCal = randomCalendar();
jps.setTime(1, time, nonDefaultCal);
assertEquals(4675000, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertEquals(TIMESTAMP, jdbcType(jps));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, time, Types.VARCHAR);
assertEquals(time.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingTimeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Time time = new Time(4675000);
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, time, Types.INTEGER));
assertEquals("Conversion from type java.sql.Time to INTEGER not supported", sqle.getMessage());
}
public void testSettingSqlDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
java.sql.Date someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
jps.setDate(1, someSqlDate);
assertEquals(someSqlDate.getTime(), ((Date)value(jps)).getTime());
assertEquals(TIMESTAMP, jdbcType(jps));
someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
Calendar nonDefaultCal = randomCalendar();
jps.setDate(1, someSqlDate, nonDefaultCal);
assertEquals(someSqlDate.getTime(), convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertEquals(TIMESTAMP, jdbcType(jps));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, someSqlDate, Types.VARCHAR);
assertEquals(someSqlDate.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingSqlDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
java.sql.Date someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class,
() -> jps.setObject(1, new java.sql.Date(randomMillisSinceEpoch()), Types.DOUBLE));
assertEquals("Conversion from type " + someSqlDate.getClass().getName() + " to DOUBLE not supported", sqle.getMessage());
}
public void testSettingCalendarValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Calendar someCalendar = randomCalendar();
someCalendar.setTimeInMillis(randomMillisSinceEpoch());
jps.setObject(1, someCalendar);
assertEquals(someCalendar.getTime(), (Date) value(jps));
assertEquals(TIMESTAMP, jdbcType(jps));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, someCalendar, Types.VARCHAR);
assertEquals(someCalendar.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
Calendar nonDefaultCal = randomCalendar();
jps.setObject(1, nonDefaultCal);
assertEquals(nonDefaultCal.getTime(), (Date) value(jps));
assertEquals(TIMESTAMP, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingCalendarValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Calendar someCalendar = randomCalendar();
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE));
assertEquals("Conversion from type " + someCalendar.getClass().getName() + " to DOUBLE not supported", sqle.getMessage());
}
public void testSettingDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Date someDate = new Date(randomMillisSinceEpoch());
jps.setObject(1, someDate);
assertEquals(someDate, (Date) value(jps));
assertEquals(TIMESTAMP, jdbcType(jps));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, someDate, Types.VARCHAR);
assertEquals(someDate.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Date someDate = new Date(randomMillisSinceEpoch());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someDate, Types.BIGINT));
assertEquals("Conversion from type " + someDate.getClass().getName() + " to BIGINT not supported", sqle.getMessage());
}
public void testSettingLocalDateTimeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
LocalDateTime ldt = LocalDateTime.now(Clock.systemDefaultZone());
jps.setObject(1, ldt);
assertEquals(Date.class, value(jps).getClass());
assertEquals(TIMESTAMP, jdbcType(jps));
assertTrue(value(jps) instanceof java.util.Date);
jps.setObject(1, ldt, Types.VARCHAR);
assertEquals(ldt.toString(), value(jps).toString());
assertEquals(VARCHAR, jdbcType(jps));
}
public void testThrownExceptionsWhenSettingLocalDateTimeValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
LocalDateTime ldt = LocalDateTime.now(Clock.systemDefaultZone());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, ldt, Types.BIGINT));
assertEquals("Conversion from type " + ldt.getClass().getName() + " to BIGINT not supported", sqle.getMessage());
}
public void testSettingByteArrayValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
byte[] buffer = "some data".getBytes(StandardCharsets.UTF_8);
jps.setBytes(1, buffer);
assertEquals(byte[].class, value(jps).getClass());
assertEquals(VARBINARY, jdbcType(jps));
jps.setObject(1, buffer);
assertEquals(byte[].class, value(jps).getClass());
assertEquals(VARBINARY, jdbcType(jps));
assertTrue(value(jps) instanceof byte[]);
jps.setObject(1, buffer, Types.VARBINARY);
assertEquals((byte[]) value(jps), buffer);
assertEquals(VARBINARY, jdbcType(jps));
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, buffer, Types.VARCHAR));
assertEquals("Conversion from type byte[] to VARCHAR not supported", sqle.getMessage());
sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, buffer, Types.DOUBLE));
assertEquals("Conversion from type byte[] to DOUBLE not supported", sqle.getMessage());
}
public void testThrownExceptionsWhenSettingByteArrayValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
byte[] buffer = "foo".getBytes(StandardCharsets.UTF_8);
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, buffer, Types.VARCHAR));
assertEquals("Conversion from type byte[] to VARCHAR not supported", sqle.getMessage());
sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, buffer, Types.DOUBLE));
assertEquals("Conversion from type byte[] to DOUBLE not supported", sqle.getMessage());
}
private long randomMillisSinceEpoch() {
return randomLongBetween(0, System.currentTimeMillis());
}
private JdbcPreparedStatement createJdbcPreparedStatement() throws SQLException {
return new JdbcPreparedStatement(null, JdbcConfiguration.create("jdbc:es://l:1", null, 0), "?");
}
private JDBCType jdbcType(JdbcPreparedStatement jps) throws SQLException {
return jps.query.getParam(1).type;
}
private Object value(JdbcPreparedStatement jps) throws SQLException {
return jps.query.getParam(1).value;
}
private Calendar randomCalendar() {
return Calendar.getInstance(randomTimeZone(), Locale.ROOT);
}
/*
* Converts from UTC to the provided Calendar.
* Helps checking if the converted date/time values using Calendars in set*(...,Calendar) methods did convert
* the values correctly to UTC.
*/
private long convertFromUTCtoCalendar(Date date, Calendar nonDefaultCal) throws SQLException {
return ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC)
.withZoneSameLocal(nonDefaultCal.getTimeZone().toZoneId())
.toInstant().toEpochMilli();
}
}

View File

@ -61,11 +61,6 @@ public enum DataType {
*/
public final JDBCType jdbcType;
/**
* Name of corresponding java class
*/
public final String javaName;
/**
* Size of the type in bytes
* <p>
@ -105,10 +100,12 @@ public enum DataType {
*/
public final boolean defaultDocValues;
private final Class<?> javaClass;
DataType(JDBCType jdbcType, Class<?> javaClass, int size, int defaultPrecision, int displaySize, boolean isInteger, boolean isRational,
boolean defaultDocValues) {
this.esType = name().toLowerCase(Locale.ROOT);
this.javaName = javaClass == null ? null : javaClass.getName();
this.javaClass = javaClass;
this.jdbcType = jdbcType;
this.size = size;
this.defaultPrecision = defaultPrecision;
@ -125,6 +122,10 @@ public enum DataType {
public String sqlName() {
return jdbcType.getName();
}
public Class<?> javaClass() {
return javaClass;
}
public boolean isNumeric() {
return isInteger || isRational;
@ -152,6 +153,13 @@ public enum DataType {
}
return jdbcToEs.get(jdbcType);
}
public static Class<?> fromJdbcTypeToJava(JDBCType jdbcType) {
if (jdbcToEs.containsKey(jdbcType) == false) {
throw new IllegalArgumentException("Unsupported JDBC type [" + jdbcType + "]");
}
return jdbcToEs.get(jdbcType).javaClass();
}
/**
* Creates returns DataType enum coresponding to the specified es type