Przeglądaj źródła

JDBC driver prepared statement set* methods (#31494)

Added setObject functionality and tests for it
Andrei Stefan 7 lat temu
rodzic
commit
400db4f37d

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

@@ -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

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

@@ -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);

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

@@ -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);
     }

+ 582 - 0
x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java

@@ -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();
+    }
+}

+ 14 - 6
x-pack/plugin/sql/sql-shared-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java

@@ -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