From 8e80894b6690eafbcf61df8e03933f07e0bba7ad Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Fri, 15 Jun 2018 20:00:33 +0300 Subject: [PATCH 1/8] Added setObject functionality and tests for it --- .../sql/jdbc/jdbc/JdbcPreparedStatement.java | 177 +- .../xpack/sql/jdbc/jdbc/JdbcResultSet.java | 2344 ++++++++--------- .../xpack/sql/jdbc/jdbc/TypeConverter.java | 37 +- .../jdbc/jdbc/JdbcPreparedStatementTests.java | 336 +++ .../xpack/sql/type/DataType.java | 14 + 5 files changed, 1703 insertions(+), 1205 deletions(-) create mode 100644 x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index b0af977c4ecfb..531df409bf011 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/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; @@ -24,11 +26,16 @@ 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.Calendar; -import java.util.Collections; class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement { final PreparedQuery query; @@ -75,67 +82,67 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException { @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); } @Override public void setDate(int parameterIndex, Date x) throws SQLException { - throw new UnsupportedOperationException("Date/Time not implemented yet"); + setObject(parameterIndex, x); } @Override public void setTime(int parameterIndex, Time x) throws SQLException { - throw new UnsupportedOperationException("Date/Time not implemented yet"); + setObject(parameterIndex, x); } @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { - throw new UnsupportedOperationException("Date/Time not implemented yet"); + setObject(parameterIndex, x); } @Override @@ -162,12 +169,22 @@ public void clearParameters() throws SQLException { @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 @@ -182,22 +199,22 @@ public void setCharacterStream(int parameterIndex, Reader reader, int length) th @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 @@ -207,17 +224,28 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { - throw new UnsupportedOperationException("Dates not implemented yet"); + setDate(parameterIndex, x); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { - throw new UnsupportedOperationException("Dates not implemented yet"); + setTime(parameterIndex, x); } @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { - throw new UnsupportedOperationException("Dates not implemented yet"); + if (cal == null) { + setTimestamp(parameterIndex, x); + return; + } + if (x == null) { + setParam(parameterIndex, null, Types.TIMESTAMP); + return; + } + + Calendar c = (Calendar) cal.clone(); + c.setTimeInMillis(x.getTime()); + setTimestamp(parameterIndex, new Timestamp(c.getTimeInMillis())); } @Override @@ -227,7 +255,7 @@ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQL @Override public void setURL(int parameterIndex, URL x) throws SQLException { - throw new SQLFeatureNotSupportedException("Datalink not supported"); + setObject(parameterIndex, x); } @Override @@ -237,7 +265,7 @@ public ParameterMetaData getParameterMetaData() throws SQLException { @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { - throw new SQLFeatureNotSupportedException("RowId not supported"); + setObject(parameterIndex, x); } @Override @@ -252,7 +280,7 @@ public void setNCharacterStream(int parameterIndex, Reader value, long length) t @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { - throw new SQLFeatureNotSupportedException("NClob not supported"); + setObject(parameterIndex, value); } @Override @@ -272,12 +300,105 @@ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLE @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 SQLException(e.getMessage()); + } + + // set the null value on the type and exit + if (x == null) { + setParam(parameterIndex, null, targetSqlType); + return; + } + + checkKnownUnsupportedTypes(x); + 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) { + try { + setParam(parameterIndex, + TypeConverter.convert(x, TypeConverter.fromJavaToJDBC(x.getClass()), DataType.fromJdbcTypeToJava(targetJDBCType)), + targetSqlType); + } catch (ClassCastException cce) { + throw new SQLException("Unable to convert " + x.getClass().getName() + " to " + targetJDBCType, cce); + } + } else if (x instanceof Timestamp + || x instanceof Calendar + || x instanceof java.util.Date + || x instanceof LocalDateTime) { + 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 java.util.Date) { + dateToSet = (java.util.Date) x; + } else { + LocalDateTime ldt = (LocalDateTime) x; + Calendar cal = Calendar.getInstance(cfg.timeZone()); + cal.set(ldt.getYear(), ldt.getMonthValue() - 1, ldt.getDayOfMonth(), ldt.getHour(), ldt.getMinute(), ldt.getSecond()); + + dateToSet = cal.getTime(); + } + + setParam(parameterIndex, dateToSet, Types.TIMESTAMP); + } else if (targetJDBCType == JDBCType.VARCHAR) { + setParam(parameterIndex, String.valueOf(x), Types.VARCHAR); + } else { + // anything 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"); + } + } else { + throw new SQLFeatureNotSupportedException("Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported"); + } + } + + private void checkKnownUnsupportedTypes(Object x) throws SQLFeatureNotSupportedException { + if (x instanceof Struct) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.Struct are not supported"); + } else if (x instanceof Array) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.Array are not supported"); + } else if (x instanceof SQLXML) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.SQLXML are not supported"); + } else if (x instanceof RowId) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.RowId are not supported"); + } else if (x instanceof Ref) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.Ref are not supported"); + } else if (x instanceof Blob) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.Blob are not supported"); + } else if (x instanceof NClob) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.NClob are not supported"); + } else if (x instanceof Clob) { + throw new SQLFeatureNotSupportedException("Objects of type java.sql.Clob are not supported"); + } else if (x instanceof LocalDate + || x instanceof LocalTime + || x instanceof OffsetTime + || x instanceof OffsetDateTime + || x instanceof java.sql.Date + || x instanceof java.sql.Time + || x instanceof URL + || x instanceof BigDecimal) { + throw new SQLFeatureNotSupportedException("Objects of type " + x.getClass().getName() + " are not supported"); + } else if (x instanceof byte[]) { + throw new UnsupportedOperationException("Bytes not implemented yet"); + } } @Override diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java index c92ac9c5ac91c..c6ccee411881a 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java @@ -1,1175 +1,1169 @@ -/* - * 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.xpack.sql.jdbc.net.client.Cursor; -import org.elasticsearch.xpack.sql.jdbc.net.protocol.ColumnInfo; -import org.elasticsearch.xpack.sql.jdbc.net.protocol.Nullable; - -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.net.URL; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Date; -import java.sql.JDBCType; -import java.sql.NClob; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.RowId; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Calendar; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.lang.String.format; - -class JdbcResultSet implements ResultSet, JdbcWrapper { - - // temporary calendar instance (per connection) used for normalizing the date and time - // even though the cfg is already in UTC format, JDBC 3.0 requires java.sql.Time to have its date - // removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed - // instead of dealing with longs, a Calendar object is used instead - private final Calendar defaultCalendar; - - private final JdbcStatement statement; - private final Cursor cursor; - private final Map nameToIndex = new LinkedHashMap<>(); - - private boolean closed = false; - private boolean wasNull = false; - - private int rowNumber; - - JdbcResultSet(JdbcConfiguration cfg, @Nullable JdbcStatement statement, Cursor cursor) { - this.statement = statement; - this.cursor = cursor; - // statement can be null so we have to extract the timeZone from the non-nullable cfg - // TODO: should we consider the locale as well? - this.defaultCalendar = Calendar.getInstance(cfg.timeZone(), Locale.ROOT); - - List columns = cursor.columns(); - for (int i = 0; i < columns.size(); i++) { - nameToIndex.put(columns.get(i).name, Integer.valueOf(i + 1)); - } - } - - private Object column(int columnIndex) throws SQLException { - checkOpen(); - if (columnIndex < 1 || columnIndex > cursor.columnSize()) { - throw new SQLException("Invalid column index [" + columnIndex + "]"); - } - Object object = null; - try { - object = cursor.column(columnIndex - 1); - } catch (IllegalArgumentException iae) { - throw new SQLException(iae.getMessage()); - } - wasNull = (object == null); - return object; - } - - private int column(String columnName) throws SQLException { - checkOpen(); - Integer index = nameToIndex.get(columnName); - if (index == null) { - throw new SQLException("Invalid column label [" + columnName + "]"); - } - return index.intValue(); - } - - void checkOpen() throws SQLException { - if (isClosed()) { - throw new SQLException("Closed result set"); - } - } - - @Override - public boolean next() throws SQLException { - checkOpen(); - if (cursor.next()) { - rowNumber++; - return true; - } - return false; - } - - @Override - public void close() throws SQLException { - if (!closed) { - closed = true; - if (statement != null) { - statement.resultSetWasClosed(); - } - cursor.close(); - } - } - - @Override - public boolean wasNull() throws SQLException { - checkOpen(); - return wasNull; - } - - @Override - public String getString(int columnIndex) throws SQLException { - Object val = column(columnIndex); - return val != null ? val.toString() : null; - } - - @Override - public boolean getBoolean(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? (Boolean) val : false; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a boolean", cce); - } - } - - @Override - public byte getByte(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).byteValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a byte", cce); - } - } - - @Override - public short getShort(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).shortValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a short", cce); - } - } - - @Override - public int getInt(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).intValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to an int", cce); - } - } - - @Override - public long getLong(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).longValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); - } - } - - @Override - public float getFloat(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).floatValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a float", cce); - } - } - - @Override - public double getDouble(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).doubleValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a double", cce); - } - } - - @Override - public byte[] getBytes(int columnIndex) throws SQLException { - try { - return (byte[]) column(columnIndex); - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a byte array", cce); - } - } - - @Override - public Date getDate(int columnIndex) throws SQLException { - return getDate(columnIndex, null); - } - - @Override - public Time getTime(int columnIndex) throws SQLException { - return getTime(columnIndex, null); - } - - @Override - public Timestamp getTimestamp(int columnIndex) throws SQLException { - return getTimestamp(columnIndex, null); - } - - @Override - public String getString(String columnLabel) throws SQLException { - return getString(column(columnLabel)); - } - - @Override - public boolean getBoolean(String columnLabel) throws SQLException { - return getBoolean(column(columnLabel)); - } - - @Override - public byte getByte(String columnLabel) throws SQLException { - return getByte(column(columnLabel)); - } - - @Override - public short getShort(String columnLabel) throws SQLException { - return getShort(column(columnLabel)); - } - - @Override - public int getInt(String columnLabel) throws SQLException { - return getInt(column(columnLabel)); - } - - @Override - public long getLong(String columnLabel) throws SQLException { - return getLong(column(columnLabel)); - } - - @Override - public float getFloat(String columnLabel) throws SQLException { - return getFloat(column(columnLabel)); - } - - @Override - public double getDouble(String columnLabel) throws SQLException { - return getDouble(column(columnLabel)); - } - - @Override - public byte[] getBytes(String columnLabel) throws SQLException { - return getBytes(column(columnLabel)); - } - - @Override - public Date getDate(String columnLabel) throws SQLException { - return getDate(column(columnLabel)); - } - - private Long dateTime(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val == null ? null : (Long) val; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); - } - } - - private Calendar safeCalendar(Calendar calendar) { - return calendar == null ? defaultCalendar : calendar; - } - - @Override - public Date getDate(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertDate(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Date getDate(String columnLabel, Calendar cal) throws SQLException { - return getDate(column(columnLabel), cal); - } - - @Override - public Time getTime(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertTime(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Time getTime(String columnLabel) throws SQLException { - return getTime(column(columnLabel)); - } - - @Override - public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertTimestamp(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Timestamp getTimestamp(String columnLabel) throws SQLException { - return getTimestamp(column(columnLabel)); - } - - @Override - public Time getTime(String columnLabel, Calendar cal) throws SQLException { - return getTime(column(columnLabel), cal); - } - - @Override - public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { - return getTimestamp(column(columnLabel), cal); - } - - @Override - public ResultSetMetaData getMetaData() throws SQLException { - return new JdbcResultSetMetaData(this, cursor.columns()); - } - - @Override - public Object getObject(int columnIndex) throws SQLException { - return convert(columnIndex, null); - } - - @Override - public T getObject(int columnIndex, Class type) throws SQLException { - if (type == null) { - throw new SQLException("type is null"); - } - - return getObject(columnIndex, type); - } - - private T convert(int columnIndex, Class type) throws SQLException { - checkOpen(); - if (columnIndex < 1 || columnIndex > cursor.columnSize()) { - throw new SQLException("Invalid column index [" + columnIndex + "]"); - } - - Object val = column(columnIndex); - - if (val == null) { - 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); - } - - @Override - public Object getObject(int columnIndex, Map> map) throws SQLException { - if (map == null || map.isEmpty()) { - return getObject(columnIndex); - } - throw new SQLFeatureNotSupportedException("getObject with non-empty Map not supported"); - } - - @Override - public Object getObject(String columnLabel) throws SQLException { - return getObject(column(columnLabel)); - } - - @Override - public T getObject(String columnLabel, Class type) throws SQLException { - return getObject(column(columnLabel), type); - } - - @Override - public Object getObject(String columnLabel, Map> map) throws SQLException { - return getObject(column(columnLabel), map); - } - - @Override - public int findColumn(String columnLabel) throws SQLException { - return column(columnLabel); - } - - @Override - public boolean isBeforeFirst() throws SQLException { - return rowNumber == 0; - } - - @Override - public boolean isAfterLast() throws SQLException { - throw new SQLFeatureNotSupportedException("isAfterLast not supported"); - } - - @Override - public boolean isFirst() throws SQLException { - return rowNumber == 1; - } - - @Override - public boolean isLast() throws SQLException { - throw new SQLFeatureNotSupportedException("isLast not supported"); - } - - @Override - public int getRow() throws SQLException { - return rowNumber; - } - - @Override - public void setFetchSize(int rows) throws SQLException { - checkOpen(); - if (rows < 0) { - throw new SQLException("Rows is negative"); - } - if (rows != getFetchSize()) { - throw new SQLException("Fetch size cannot be changed"); - } - // ignore fetch size since scrolls cannot be changed in flight - } - - @Override - public int getFetchSize() throws SQLException { - /* - * Instead of returning the fetch size the user requested we make a - * stab at returning the fetch size that we actually used, returning - * the batch size of the current row. This allows us to assert these - * batch sizes in testing and lets us point users to something that - * they can use for debugging. - */ - checkOpen(); - return cursor.batchSize(); - } - - @Override - public Statement getStatement() throws SQLException { - checkOpen(); - return statement; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - @Deprecated - public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public InputStream getAsciiStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("AsciiStream not supported"); - } - - @Override - @Deprecated - public InputStream getUnicodeStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); - } - - @Override - public InputStream getBinaryStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("BinaryStream not supported"); - } - - @Override - @Deprecated - public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public InputStream getAsciiStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("AsciiStream not supported"); - } - - @Override - @Deprecated - public InputStream getUnicodeStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); - } - - @Override - public InputStream getBinaryStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("BinaryStream not supported"); - } - - @Override - public SQLWarning getWarnings() throws SQLException { - checkOpen(); - return null; - } - - @Override - public void clearWarnings() throws SQLException { - checkOpen(); - } - - @Override - public String getCursorName() throws SQLException { - throw new SQLFeatureNotSupportedException("Cursor name not supported"); - } - - @Override - public Reader getCharacterStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("CharacterStream not supported"); - } - - @Override - public Reader getCharacterStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("CharacterStream not supported"); - } - - @Override - public BigDecimal getBigDecimal(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public BigDecimal getBigDecimal(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public void beforeFirst() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public void afterLast() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean first() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean last() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean absolute(int row) throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean relative(int rows) throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean previous() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public int getType() throws SQLException { - checkOpen(); - return TYPE_FORWARD_ONLY; - } - - @Override - public int getConcurrency() throws SQLException { - checkOpen(); - return CONCUR_READ_ONLY; - } - - @Override - public void setFetchDirection(int direction) throws SQLException { - checkOpen(); - if (direction != FETCH_FORWARD) { - throw new SQLException("Fetch direction must be FETCH_FORWARD"); - } - } - - @Override - public int getFetchDirection() throws SQLException { - checkOpen(); - return FETCH_FORWARD; - } - - @Override - public boolean rowUpdated() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public boolean rowInserted() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public boolean rowDeleted() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNull(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBoolean(int columnIndex, boolean x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateByte(int columnIndex, byte x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateShort(int columnIndex, short x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateInt(int columnIndex, int x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateLong(int columnIndex, long x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateFloat(int columnIndex, float x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDouble(int columnIndex, double x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateString(int columnIndex, String x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBytes(int columnIndex, byte[] x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDate(int columnIndex, Date x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTime(int columnIndex, Time x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(int columnIndex, Object x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNull(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBoolean(String columnLabel, boolean x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateByte(String columnLabel, byte x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateShort(String columnLabel, short x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateInt(String columnLabel, int x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateLong(String columnLabel, long x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateFloat(String columnLabel, float x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDouble(String columnLabel, double x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateString(String columnLabel, String x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBytes(String columnLabel, byte[] x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDate(String columnLabel, Date x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTime(String columnLabel, Time x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(String columnLabel, Object x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void insertRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void deleteRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void cancelRowUpdates() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void moveToInsertRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void refreshRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void moveToCurrentRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public Ref getRef(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Ref not supported"); - } - - @Override - public Blob getBlob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Blob not supported"); - } - - @Override - public Clob getClob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Clob not supported"); - } - - @Override - public Array getArray(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Array not supported"); - } - - @Override - public Ref getRef(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Ref not supported"); - } - - @Override - public Blob getBlob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Blob not supported"); - } - - @Override - public Clob getClob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Clob not supported"); - } - - @Override - public Array getArray(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Array not supported"); - } - - @Override - public URL getURL(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("URL not supported"); - } - - @Override - public URL getURL(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("URL not supported"); - } - - @Override - public void updateRef(int columnIndex, Ref x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRef(String columnLabel, Ref x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, Blob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, Blob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Clob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Clob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateArray(int columnIndex, Array x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateArray(String columnLabel, Array x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public RowId getRowId(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("RowId not supported"); - } - - @Override - public RowId getRowId(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("RowId not supported"); - } - - @Override - public void updateRowId(int columnIndex, RowId x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRowId(String columnLabel, RowId x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public int getHoldability() throws SQLException { - checkOpen(); - return HOLD_CURSORS_OVER_COMMIT; - } - - @Override - public void updateNString(int columnIndex, String nString) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNString(String columnLabel, String nString) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, NClob nClob) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, NClob nClob) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public NClob getNClob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NClob not supported"); - } - - @Override - public NClob getNClob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NClob not supported"); - } - - @Override - public SQLXML getSQLXML(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("SQLXML not supported"); - } - - @Override - public SQLXML getSQLXML(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("SQLXML not supported"); - } - - @Override - public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public String getNString(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NString not supported"); - } - - @Override - public String getNString(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NString not supported"); - } - - @Override - public Reader getNCharacterStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); - } - - @Override - public Reader getNCharacterStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); - } - - @Override - public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public String toString() { - return format(Locale.ROOT, "%s:row %d", getClass().getSimpleName(), rowNumber); - } -} +/* + * 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.xpack.sql.jdbc.net.client.Cursor; +import org.elasticsearch.xpack.sql.jdbc.net.protocol.ColumnInfo; +import org.elasticsearch.xpack.sql.jdbc.net.protocol.Nullable; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.lang.String.format; + +class JdbcResultSet implements ResultSet, JdbcWrapper { + + // temporary calendar instance (per connection) used for normalizing the date and time + // even though the cfg is already in UTC format, JDBC 3.0 requires java.sql.Time to have its date + // removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed + // instead of dealing with longs, a Calendar object is used instead + private final Calendar defaultCalendar; + + private final JdbcStatement statement; + private final Cursor cursor; + private final Map nameToIndex = new LinkedHashMap<>(); + + private boolean closed = false; + private boolean wasNull = false; + + private int rowNumber; + + JdbcResultSet(JdbcConfiguration cfg, @Nullable JdbcStatement statement, Cursor cursor) { + this.statement = statement; + this.cursor = cursor; + // statement can be null so we have to extract the timeZone from the non-nullable cfg + // TODO: should we consider the locale as well? + this.defaultCalendar = Calendar.getInstance(cfg.timeZone(), Locale.ROOT); + + List columns = cursor.columns(); + for (int i = 0; i < columns.size(); i++) { + nameToIndex.put(columns.get(i).name, Integer.valueOf(i + 1)); + } + } + + private Object column(int columnIndex) throws SQLException { + checkOpen(); + if (columnIndex < 1 || columnIndex > cursor.columnSize()) { + throw new SQLException("Invalid column index [" + columnIndex + "]"); + } + Object object = null; + try { + object = cursor.column(columnIndex - 1); + } catch (IllegalArgumentException iae) { + throw new SQLException(iae.getMessage()); + } + wasNull = (object == null); + return object; + } + + private int column(String columnName) throws SQLException { + checkOpen(); + Integer index = nameToIndex.get(columnName); + if (index == null) { + throw new SQLException("Invalid column label [" + columnName + "]"); + } + return index.intValue(); + } + + void checkOpen() throws SQLException { + if (isClosed()) { + throw new SQLException("Closed result set"); + } + } + + @Override + public boolean next() throws SQLException { + checkOpen(); + if (cursor.next()) { + rowNumber++; + return true; + } + return false; + } + + @Override + public void close() throws SQLException { + if (!closed) { + closed = true; + if (statement != null) { + statement.resultSetWasClosed(); + } + cursor.close(); + } + } + + @Override + public boolean wasNull() throws SQLException { + checkOpen(); + return wasNull; + } + + @Override + public String getString(int columnIndex) throws SQLException { + Object val = column(columnIndex); + return val != null ? val.toString() : null; + } + + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? (Boolean) val : false; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a boolean", cce); + } + } + + @Override + public byte getByte(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).byteValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a byte", cce); + } + } + + @Override + public short getShort(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).shortValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a short", cce); + } + } + + @Override + public int getInt(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).intValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to an int", cce); + } + } + + @Override + public long getLong(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).longValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); + } + } + + @Override + public float getFloat(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).floatValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a float", cce); + } + } + + @Override + public double getDouble(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).doubleValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a double", cce); + } + } + + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + try { + return (byte[]) column(columnIndex); + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a byte array", cce); + } + } + + @Override + public Date getDate(int columnIndex) throws SQLException { + return getDate(columnIndex, null); + } + + @Override + public Time getTime(int columnIndex) throws SQLException { + return getTime(columnIndex, null); + } + + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + return getTimestamp(columnIndex, null); + } + + @Override + public String getString(String columnLabel) throws SQLException { + return getString(column(columnLabel)); + } + + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + return getBoolean(column(columnLabel)); + } + + @Override + public byte getByte(String columnLabel) throws SQLException { + return getByte(column(columnLabel)); + } + + @Override + public short getShort(String columnLabel) throws SQLException { + return getShort(column(columnLabel)); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + return getInt(column(columnLabel)); + } + + @Override + public long getLong(String columnLabel) throws SQLException { + return getLong(column(columnLabel)); + } + + @Override + public float getFloat(String columnLabel) throws SQLException { + return getFloat(column(columnLabel)); + } + + @Override + public double getDouble(String columnLabel) throws SQLException { + return getDouble(column(columnLabel)); + } + + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + return getBytes(column(columnLabel)); + } + + @Override + public Date getDate(String columnLabel) throws SQLException { + return getDate(column(columnLabel)); + } + + private Long dateTime(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val == null ? null : (Long) val; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); + } + } + + private Calendar safeCalendar(Calendar calendar) { + return calendar == null ? defaultCalendar : calendar; + } + + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertDate(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException { + return getDate(column(columnLabel), cal); + } + + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertTime(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Time getTime(String columnLabel) throws SQLException { + return getTime(column(columnLabel)); + } + + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertTimestamp(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + return getTimestamp(column(columnLabel)); + } + + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException { + return getTime(column(columnLabel), cal); + } + + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { + return getTimestamp(column(columnLabel), cal); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return new JdbcResultSetMetaData(this, cursor.columns()); + } + + @Override + public Object getObject(int columnIndex) throws SQLException { + return convert(columnIndex, null); + } + + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + if (type == null) { + throw new SQLException("type is null"); + } + + return getObject(columnIndex, type); + } + + private T convert(int columnIndex, Class type) throws SQLException { + checkOpen(); + if (columnIndex < 1 || columnIndex > cursor.columnSize()) { + throw new SQLException("Invalid column index [" + columnIndex + "]"); + } + + Object val = column(columnIndex); + + if (val == null) { + return null; + } + + JDBCType columnType = cursor.columns().get(columnIndex - 1).type; try { + return TypeConverter.convert(val, columnType, type); + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to " + type, cce); + } + } + + @Override + public Object getObject(int columnIndex, Map> map) throws SQLException { + if (map == null || map.isEmpty()) { + return getObject(columnIndex); + } + throw new SQLFeatureNotSupportedException("getObject with non-empty Map not supported"); + } + + @Override + public Object getObject(String columnLabel) throws SQLException { + return getObject(column(columnLabel)); + } + + @Override + public T getObject(String columnLabel, Class type) throws SQLException { + return getObject(column(columnLabel), type); + } + + @Override + public Object getObject(String columnLabel, Map> map) throws SQLException { + return getObject(column(columnLabel), map); + } + + @Override + public int findColumn(String columnLabel) throws SQLException { + return column(columnLabel); + } + + @Override + public boolean isBeforeFirst() throws SQLException { + return rowNumber == 0; + } + + @Override + public boolean isAfterLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isAfterLast not supported"); + } + + @Override + public boolean isFirst() throws SQLException { + return rowNumber == 1; + } + + @Override + public boolean isLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isLast not supported"); + } + + @Override + public int getRow() throws SQLException { + return rowNumber; + } + + @Override + public void setFetchSize(int rows) throws SQLException { + checkOpen(); + if (rows < 0) { + throw new SQLException("Rows is negative"); + } + if (rows != getFetchSize()) { + throw new SQLException("Fetch size cannot be changed"); + } + // ignore fetch size since scrolls cannot be changed in flight + } + + @Override + public int getFetchSize() throws SQLException { + /* + * Instead of returning the fetch size the user requested we make a + * stab at returning the fetch size that we actually used, returning + * the batch size of the current row. This allows us to assert these + * batch sizes in testing and lets us point users to something that + * they can use for debugging. + */ + checkOpen(); + return cursor.batchSize(); + } + + @Override + public Statement getStatement() throws SQLException { + checkOpen(); + return statement; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + @Deprecated + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("AsciiStream not supported"); + } + + @Override + @Deprecated + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); + } + + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("BinaryStream not supported"); + } + + @Override + @Deprecated + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("AsciiStream not supported"); + } + + @Override + @Deprecated + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); + } + + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("BinaryStream not supported"); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + checkOpen(); + return null; + } + + @Override + public void clearWarnings() throws SQLException { + checkOpen(); + } + + @Override + public String getCursorName() throws SQLException { + throw new SQLFeatureNotSupportedException("Cursor name not supported"); + } + + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("CharacterStream not supported"); + } + + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("CharacterStream not supported"); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public void beforeFirst() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public void afterLast() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean first() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean last() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean absolute(int row) throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean relative(int rows) throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean previous() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public int getType() throws SQLException { + checkOpen(); + return TYPE_FORWARD_ONLY; + } + + @Override + public int getConcurrency() throws SQLException { + checkOpen(); + return CONCUR_READ_ONLY; + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + checkOpen(); + if (direction != FETCH_FORWARD) { + throw new SQLException("Fetch direction must be FETCH_FORWARD"); + } + } + + @Override + public int getFetchDirection() throws SQLException { + checkOpen(); + return FETCH_FORWARD; + } + + @Override + public boolean rowUpdated() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public boolean rowInserted() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public boolean rowDeleted() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNull(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateString(int columnIndex, String x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNull(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateString(String columnLabel, String x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void insertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void deleteRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void cancelRowUpdates() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void moveToInsertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void refreshRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void moveToCurrentRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public Ref getRef(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Ref not supported"); + } + + @Override + public Blob getBlob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob not supported"); + } + + @Override + public Clob getClob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob not supported"); + } + + @Override + public Array getArray(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Array not supported"); + } + + @Override + public Ref getRef(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Ref not supported"); + } + + @Override + public Blob getBlob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob not supported"); + } + + @Override + public Clob getClob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob not supported"); + } + + @Override + public Array getArray(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Array not supported"); + } + + @Override + public URL getURL(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("URL not supported"); + } + + @Override + public URL getURL(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("URL not supported"); + } + + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public RowId getRowId(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("RowId not supported"); + } + + @Override + public RowId getRowId(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("RowId not supported"); + } + + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public int getHoldability() throws SQLException { + checkOpen(); + return HOLD_CURSORS_OVER_COMMIT; + } + + @Override + public void updateNString(int columnIndex, String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNString(String columnLabel, String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public NClob getNClob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NClob not supported"); + } + + @Override + public NClob getNClob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NClob not supported"); + } + + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("SQLXML not supported"); + } + + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("SQLXML not supported"); + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public String getNString(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NString not supported"); + } + + @Override + public String getNString(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NString not supported"); + } + + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); + } + + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public String toString() { + return format(Locale.ROOT, "%s:row %d", getClass().getSimpleName(), rowNumber); + } +} diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java index a1fa04ef1afd0..45ebd563be5e1 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java @@ -11,6 +11,7 @@ import java.sql.Date; import java.sql.JDBCType; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDate; @@ -18,10 +19,13 @@ import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; +import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; +import java.util.Map; 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 +52,18 @@ private TypeConverter() { } private static final long DAY_IN_MILLIS = 60 * 60 * 24; + private static final Map, JDBCType> javaToJDBC; + + static { + javaToJDBC = 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 + javaToJDBC.put(Calendar.class, JDBCType.TIMESTAMP); + javaToJDBC.put(java.util.Date.class, JDBCType.TIMESTAMP); + javaToJDBC.put(LocalDateTime.class, JDBCType.TIMESTAMP); + } /** * Converts millisecond after epoc to date @@ -99,10 +115,15 @@ private static T dateTimeConvert(Long millis, Calendar c, Function T convert(Object val, JDBCType columnType, Class type) throws SQLException { + static T convert(Object val, JDBCType columnType, Class type) throws SQLException, ClassCastException { if (type == null) { return (T) convert(val, columnType); } + + if (type.isInstance(val)) { + return type.cast(val); + } + if (type == String.class) { return (T) asString(convert(val, columnType)); } @@ -228,6 +249,18 @@ static boolean isSigned(JDBCType jdbcType) throws SQLException { } return dataType.isSigned(); } + + + static JDBCType fromJavaToJDBC(Class clazz) throws SQLException { + for (Class key : javaToJDBC.keySet()) { + // java.util.Calendar from {@code javaToJDBC} is an abstract class and this method can be used with concrete classes as well + if (key.isAssignableFrom(clazz)) { + return javaToJDBC.get(key); + } + } + + throw new SQLFeatureNotSupportedException("Objects of type " + clazz.getName() + " are not supported"); + } private static Double doubleValue(Object v) { if (v instanceof String) { @@ -275,7 +308,7 @@ private static Boolean asBoolean(Object val, JDBCType columnType) throws SQLExce 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"); diff --git a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java new file mode 100644 index 0000000000000..1f867c0cfdc92 --- /dev/null +++ b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java @@ -0,0 +1,336 @@ +/* + * 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.sql.JDBCType; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Struct; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +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.VARCHAR; + +public class JdbcPreparedStatementTests extends ESTestCase { + + public void testBooleanSetters() 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)); + + 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)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, true, Types.TIMESTAMP)); + assertEquals("Conversion from type [BOOLEAN] to [Timestamp] not supported", sqle.getMessage()); + } + + public void testStringSetters() 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)); + + 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 testByteSetters() 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)); + + 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)); + + 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 testShortSetters() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); + + jps.setShort(1, (short) 7); + assertEquals((short) 7, value(jps)); + assertEquals(SMALLINT, jdbcType(jps)); + + jps.setObject(1, (short) 7); + assertEquals((short) 7, value(jps)); + assertEquals(SMALLINT, jdbcType(jps)); + + jps.setObject(1, (short) 1, Types.BOOLEAN); + assertEquals(true, value(jps)); + assertEquals(BOOLEAN, jdbcType(jps)); + + jps.setObject(1, (short) 123, Types.DOUBLE); + assertEquals(123.0, value(jps)); + assertEquals(DOUBLE, jdbcType(jps)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (short) 6, Types.TIMESTAMP)); + assertEquals("Conversion from type [SMALLINT] to [Timestamp] not supported", sqle.getMessage()); + } + + public void testIntegerSetters() 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)); + + 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)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someInt, Types.TIMESTAMP)); + assertEquals("Conversion from type [INTEGER] to [Timestamp] not supported", sqle.getMessage()); + } + + public void testLongSetters() 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)); + + jps.setObject(1, someLong, Types.VARCHAR); + assertEquals(String.valueOf(someLong), value(jps)); + assertEquals(VARCHAR, jdbcType(jps)); + + jps.setObject(1, someLong, Types.DOUBLE); + assertEquals(new Double(someLong), value(jps)); + assertEquals(DOUBLE, jdbcType(jps)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someLong, Types.TIMESTAMP)); + assertEquals("Conversion from type [BIGINT] to [Timestamp] not supported", sqle.getMessage()); + } + + public void testFloatSetters() 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)); + + jps.setObject(1, someFloat, Types.VARCHAR); + assertEquals(String.valueOf(someFloat), value(jps)); + assertEquals(VARCHAR, jdbcType(jps)); + + jps.setObject(1, someFloat, Types.DOUBLE); + assertEquals(new Double(someFloat), value(jps)); + assertEquals(DOUBLE, jdbcType(jps)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someFloat, Types.TIMESTAMP)); + assertEquals("Conversion from type [REAL] to [Timestamp] not supported", sqle.getMessage()); + } + + public void testDoubleSetters() 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)); + + 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)); + + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someDouble, Types.TIMESTAMP)); + assertEquals("Conversion from type [DOUBLE] to [Timestamp] not supported", 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> 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()); + } + + public void testDateTimeSetters() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); + + // Timestamp + Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch()); + jps.setTimestamp(1, someTimestamp); + assertEquals(someTimestamp.getTime(), ((Date)value(jps)).getTime()); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, someTimestamp, Types.VARCHAR); + assertEquals(someTimestamp.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someTimestamp, Types.INTEGER)); + assertEquals("Conversion from type java.sql.Timestamp to INTEGER not supported", sqle.getMessage()); + + // Calendar + Calendar someCalendar = Calendar.getInstance(); + someCalendar.setTimeInMillis(randomMillisSinceEpoch()); + + jps.setObject(1, someCalendar); + assertEquals(someCalendar.getTime(), (Date) value(jps)); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, someCalendar, Types.VARCHAR); + assertEquals(someCalendar.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE)); + assertEquals("Conversion from type " + someCalendar.getClass().getName() + " to DOUBLE not supported", sqle.getMessage()); + + Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + jps.setObject(1, nonDefaultCal); + assertEquals(nonDefaultCal.getTime(), (Date) value(jps)); + assertEquals(TIMESTAMP, jdbcType(jps)); + + // java.util.Date + Date someDate = new Date(randomMillisSinceEpoch()); + + jps.setObject(1, someDate); + assertEquals(someDate, (Date) value(jps)); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, someDate, Types.VARCHAR); + assertEquals(someDate.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someDate, Types.BIGINT)); + assertEquals("Conversion from type " + someDate.getClass().getName() + " to BIGINT not supported", sqle.getMessage()); + + // java.time.LocalDateTime + LocalDateTime ldt = LocalDateTime.now(); + + jps.setObject(1, ldt); + assertEquals(Date.class, value(jps).getClass()); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, ldt, Types.VARCHAR); + assertEquals(ldt.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, ldt, Types.BIGINT)); + assertEquals("Conversion from type " + ldt.getClass().getName() + " to BIGINT 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; + } +} diff --git a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java b/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java index c024af48187d3..5c4fc4a43eb56 100644 --- a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java +++ b/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java @@ -105,10 +105,13 @@ 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 +128,10 @@ public enum DataType { public String sqlName() { return jdbcType.getName(); } + + public Class javaClass() { + return javaClass; + } public boolean isNumeric() { return isInteger || isRational; @@ -152,6 +159,13 @@ public static DataType fromJdbcType(JDBCType jdbcType) { } 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 From 2746d2e2f01bfe833f2f84e694526cb4aefeca5e Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Fri, 15 Jun 2018 20:13:32 +0300 Subject: [PATCH 2/8] Minor changes --- .../xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java | 6 ++++-- .../xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index 531df409bf011..6610c45add3ec 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java @@ -363,10 +363,12 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale setParam(parameterIndex, String.valueOf(x), Types.VARCHAR); } else { // anything 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"); + throw new SQLFeatureNotSupportedException( + "Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported"); } } else { - throw new SQLFeatureNotSupportedException("Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported"); + throw new SQLFeatureNotSupportedException( + "Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported"); } } diff --git a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java index 1f867c0cfdc92..49ad25e00dc18 100644 --- a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java +++ b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java @@ -18,7 +18,6 @@ import java.util.Calendar; import java.util.Date; import java.util.Map; -import java.util.TimeZone; import static java.sql.JDBCType.BIGINT; import static java.sql.JDBCType.BOOLEAN; From 9022d66c7771289c87a109d528716d5edd062fb4 Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Wed, 20 Jun 2018 13:18:12 +0300 Subject: [PATCH 3/8] Added the Calendar conversion to UTC for time based setters. Also, added the byte[] setter. --- .../sql/jdbc/jdbc/JdbcPreparedStatement.java | 91 +++++---- .../xpack/sql/jdbc/jdbc/TypeConverter.java | 29 ++- .../jdbc/jdbc/JdbcPreparedStatementTests.java | 174 ++++++++++++++++-- 3 files changed, 233 insertions(+), 61 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index 6610c45add3ec..b27e693d149e5 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java @@ -35,7 +35,10 @@ 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; class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement { final PreparedQuery query; @@ -224,12 +227,28 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { - setDate(parameterIndex, x); + if (cal == null) { + setDate(parameterIndex, x); + return; + } + if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { + return; + } + + setDate(parameterIndex, new Date(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { - setTime(parameterIndex, x); + if (cal == null) { + setTime(parameterIndex, x); + return; + } + if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { + return; + } + + setTime(parameterIndex, new Time(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); } @Override @@ -238,14 +257,11 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S setTimestamp(parameterIndex, x); return; } - if (x == null) { - setParam(parameterIndex, null, Types.TIMESTAMP); + if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { return; } - Calendar c = (Calendar) cal.clone(); - c.setTimeInMillis(x.getTime()); - setTimestamp(parameterIndex, new Timestamp(c.getTimeInMillis())); + setTimestamp(parameterIndex, new Timestamp(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); } @Override @@ -340,7 +356,9 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale } else if (x instanceof Timestamp || x instanceof Calendar || x instanceof java.util.Date - || x instanceof LocalDateTime) { + || x instanceof LocalDateTime + || x instanceof Date + || x instanceof Time) { 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; @@ -350,22 +368,32 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale dateToSet = ((Calendar) x).getTime(); } else if (x instanceof java.util.Date) { dateToSet = (java.util.Date) x; - } else { + } else if (x instanceof LocalDateTime){ LocalDateTime ldt = (LocalDateTime) x; Calendar cal = Calendar.getInstance(cfg.timeZone()); cal.set(ldt.getYear(), ldt.getMonthValue() - 1, ldt.getDayOfMonth(), ldt.getHour(), ldt.getMinute(), ldt.getSecond()); dateToSet = cal.getTime(); + } else if (x instanceof Date) { + dateToSet = TypeConverter.convertDate(((Date) x).getTime(), Calendar.getInstance(cfg.timeZone())); + } else { + dateToSet = TypeConverter.convertDate(((Time) x).getTime(), Calendar.getInstance(cfg.timeZone())); } setParam(parameterIndex, dateToSet, Types.TIMESTAMP); } else if (targetJDBCType == JDBCType.VARCHAR) { setParam(parameterIndex, String.valueOf(x), Types.VARCHAR); } else { - // anything other than VARCHAR and TIMESTAMP is not supported in this JDBC driver + // 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"); } + } else if (x instanceof byte[]) { + if (targetJDBCType != JDBCType.VARBINARY) { + throw new SQLFeatureNotSupportedException( + "Conversion from type byte[] to " + targetJDBCType + " not supported"); + } + setParam(parameterIndex, x, Types.VARBINARY); } else { throw new SQLFeatureNotSupportedException( "Conversion from type " + x.getClass().getName() + " to " + targetJDBCType + " not supported"); @@ -373,33 +401,14 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale } private void checkKnownUnsupportedTypes(Object x) throws SQLFeatureNotSupportedException { - if (x instanceof Struct) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.Struct are not supported"); - } else if (x instanceof Array) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.Array are not supported"); - } else if (x instanceof SQLXML) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.SQLXML are not supported"); - } else if (x instanceof RowId) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.RowId are not supported"); - } else if (x instanceof Ref) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.Ref are not supported"); - } else if (x instanceof Blob) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.Blob are not supported"); - } else if (x instanceof NClob) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.NClob are not supported"); - } else if (x instanceof Clob) { - throw new SQLFeatureNotSupportedException("Objects of type java.sql.Clob are not supported"); - } else if (x instanceof LocalDate - || x instanceof LocalTime - || x instanceof OffsetTime - || x instanceof OffsetDateTime - || x instanceof java.sql.Date - || x instanceof java.sql.Time - || x instanceof URL - || x instanceof BigDecimal) { - throw new SQLFeatureNotSupportedException("Objects of type " + x.getClass().getName() + " are not supported"); - } else if (x instanceof byte[]) { - throw new UnsupportedOperationException("Bytes not implemented yet"); + List> unsupportedTypes = new ArrayList>(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"); + } } } @@ -502,4 +511,12 @@ public boolean execute(String sql, String[] columnNames) throws SQLException { public long executeLargeUpdate() throws SQLException { throw new SQLFeatureNotSupportedException("Batching not supported"); } + + private boolean checkNull(int parameterIndex, Object o, int type) throws SQLException { + if (o == null) { + setNull(parameterIndex, type); + return true; + } + return false; + } } \ No newline at end of file diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java index 45ebd563be5e1..0890555860874 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java @@ -19,6 +19,8 @@ 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.GregorianCalendar; @@ -56,7 +58,10 @@ private TypeConverter() { static { javaToJDBC = Arrays.stream(DataType.values()) - .filter(dataType -> dataType.javaClass() != null && dataType != DataType.HALF_FLOAT && dataType != DataType.SCALED_FLOAT && dataType != DataType.TEXT) + .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 @@ -110,6 +115,20 @@ private static T dateTimeConvert(Long millis, Calendar c, Function 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); } diff --git a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java index 49ad25e00dc18..a9fbd7c4ba670 100644 --- a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java +++ b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java @@ -12,11 +12,13 @@ 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.LocalDateTime; import java.util.Calendar; import java.util.Date; +import java.util.Locale; import java.util.Map; import static java.sql.JDBCType.BIGINT; @@ -29,6 +31,7 @@ import static java.sql.JDBCType.TIMESTAMP; import static java.sql.JDBCType.TINYINT; import static java.sql.JDBCType.VARCHAR; +import static java.sql.JDBCType.VARBINARY;; public class JdbcPreparedStatementTests extends ESTestCase { @@ -97,6 +100,14 @@ public void testByteSetters() throws SQLException { 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)); + SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (byte) 6, Types.TIMESTAMP)); assertEquals("Conversion from type [TINYINT] to [Timestamp] not supported", sqle.getMessage()); } @@ -104,24 +115,32 @@ public void testByteSetters() throws SQLException { public void testShortSetters() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); - jps.setShort(1, (short) 7); - assertEquals((short) 7, value(jps)); + short someShort = randomShort(); + jps.setShort(1, someShort); + assertEquals(someShort, value(jps)); assertEquals(SMALLINT, jdbcType(jps)); - jps.setObject(1, (short) 7); - assertEquals((short) 7, value(jps)); + jps.setObject(1, someShort); + assertEquals(someShort, value(jps)); assertEquals(SMALLINT, jdbcType(jps)); jps.setObject(1, (short) 1, Types.BOOLEAN); assertEquals(true, value(jps)); assertEquals(BOOLEAN, jdbcType(jps)); - jps.setObject(1, (short) 123, Types.DOUBLE); - assertEquals(123.0, value(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)); + 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 testIntegerSetters() throws SQLException { @@ -147,6 +166,13 @@ public void testIntegerSetters() throws SQLException { 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 testLongSetters() throws SQLException { @@ -166,11 +192,22 @@ public void testLongSetters() throws SQLException { assertEquals(VARCHAR, jdbcType(jps)); jps.setObject(1, someLong, Types.DOUBLE); - assertEquals(new Double(someLong), value(jps)); + assertEquals((double) someLong, value(jps)); assertEquals(DOUBLE, jdbcType(jps)); + jps.setObject(1, someLong, Types.FLOAT); + assertEquals((double) someLong, value(jps)); + assertEquals(FLOAT, jdbcType(jps)); + 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 testFloatSetters() throws SQLException { @@ -190,11 +227,24 @@ public void testFloatSetters() throws SQLException { assertEquals(VARCHAR, jdbcType(jps)); jps.setObject(1, someFloat, Types.DOUBLE); - assertEquals(new Double(someFloat), value(jps)); + assertEquals((double) someFloat, value(jps)); assertEquals(DOUBLE, jdbcType(jps)); + jps.setObject(1, someFloat, Types.FLOAT); + assertEquals((double) someFloat, value(jps)); + assertEquals(FLOAT, jdbcType(jps)); + 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 testDoubleSetters() throws SQLException { @@ -219,6 +269,11 @@ public void testDoubleSetters() throws SQLException { 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 { @@ -250,25 +305,79 @@ public Object[] getAttributes() throws SQLException { 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 testDateTimeSetters() throws SQLException { + public void testTimestamp() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); - - // Timestamp + Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch()); jps.setTimestamp(1, someTimestamp); assertEquals(someTimestamp.getTime(), ((Date)value(jps)).getTime()); assertEquals(TIMESTAMP, jdbcType(jps)); + Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + // February 29th, 2016. 01:17:55 GMT = 1456708675000 millis since epoch + jps.setTimestamp(1, new Timestamp(1456708675000L), nonDefaultCal); + assertEquals(1456708675000L + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + assertEquals(TIMESTAMP, jdbcType(jps)); + + long beforeEpochTime = -randomMillisSinceEpoch(); + jps.setTimestamp(1, new Timestamp(beforeEpochTime), nonDefaultCal); + assertEquals(beforeEpochTime + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + jps.setObject(1, someTimestamp, Types.VARCHAR); assertEquals(someTimestamp.toString(), value(jps).toString()); assertEquals(VARCHAR, jdbcType(jps)); 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 testTime() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); - // Calendar + Time time = new Time(4675000); + Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + jps.setTime(1, time, nonDefaultCal); + assertEquals(4675000 + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, time, Types.VARCHAR); + assertEquals(time.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + 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 testSqlDate() 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 = Calendar.getInstance(randomTimeZone()); + jps.setDate(1, someSqlDate, nonDefaultCal); + assertEquals(someSqlDate.getTime() + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + assertEquals(TIMESTAMP, jdbcType(jps)); + + jps.setObject(1, someSqlDate, Types.VARCHAR); + assertEquals(someSqlDate.toString(), value(jps).toString()); + assertEquals(VARCHAR, jdbcType(jps)); + + 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 testCalendar() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); Calendar someCalendar = Calendar.getInstance(); someCalendar.setTimeInMillis(randomMillisSinceEpoch()); @@ -280,15 +389,17 @@ public void testDateTimeSetters() throws SQLException { assertEquals(someCalendar.toString(), value(jps).toString()); assertEquals(VARCHAR, jdbcType(jps)); - sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE)); + SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE)); assertEquals("Conversion from type " + someCalendar.getClass().getName() + " to DOUBLE not supported", sqle.getMessage()); Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); jps.setObject(1, nonDefaultCal); assertEquals(nonDefaultCal.getTime(), (Date) value(jps)); assertEquals(TIMESTAMP, jdbcType(jps)); - - // java.util.Date + } + + public void testDate() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); Date someDate = new Date(randomMillisSinceEpoch()); jps.setObject(1, someDate); @@ -299,10 +410,12 @@ public void testDateTimeSetters() throws SQLException { assertEquals(someDate.toString(), value(jps).toString()); assertEquals(VARCHAR, jdbcType(jps)); - sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someDate, Types.BIGINT)); + SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someDate, Types.BIGINT)); assertEquals("Conversion from type " + someDate.getClass().getName() + " to BIGINT not supported", sqle.getMessage()); - - // java.time.LocalDateTime + } + + public void testLocalDateTime() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); LocalDateTime ldt = LocalDateTime.now(); jps.setObject(1, ldt); @@ -313,9 +426,32 @@ public void testDateTimeSetters() throws SQLException { assertEquals(ldt.toString(), value(jps).toString()); assertEquals(VARCHAR, jdbcType(jps)); - sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, ldt, Types.BIGINT)); + 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 testBytesSetter() throws SQLException { + JdbcPreparedStatement jps = createJdbcPreparedStatement(); + + byte[] buffer = "some data".getBytes(); + 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)); + + 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()); + } private long randomMillisSinceEpoch() { return randomLongBetween(0, System.currentTimeMillis()); From 31cbdca495580e708c0bae8255f9bd24aac550da Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Wed, 20 Jun 2018 13:53:18 +0300 Subject: [PATCH 4/8] Removed DataType.javaName redundant information --- .../elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java | 4 ++-- .../java/org/elasticsearch/xpack/sql/type/DataType.java | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java index 0890555860874..b38d372e26414 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java @@ -214,10 +214,10 @@ public static String classNameOf(JDBCType jdbcType) throws JdbcSQLException { // 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(); } /** diff --git a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java b/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java index 5c4fc4a43eb56..3f77bc2fc2ed7 100644 --- a/x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java +++ b/x-pack/plugin/sql/sql-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 *

@@ -110,7 +105,6 @@ public enum DataType { 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; From 4a076cc41c0ff86c6b69e30a395903edc7784a58 Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Thu, 21 Jun 2018 07:48:50 +0300 Subject: [PATCH 5/8] Fixed spacing on one line --- .../xpack/sql/jdbc/jdbc/JdbcResultSet.java | 2339 +++++++++-------- 1 file changed, 1170 insertions(+), 1169 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java index c6ccee411881a..5736dcb25224d 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java @@ -1,1169 +1,1170 @@ -/* - * 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.xpack.sql.jdbc.net.client.Cursor; -import org.elasticsearch.xpack.sql.jdbc.net.protocol.ColumnInfo; -import org.elasticsearch.xpack.sql.jdbc.net.protocol.Nullable; - -import java.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.net.URL; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Date; -import java.sql.JDBCType; -import java.sql.NClob; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.RowId; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Calendar; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.lang.String.format; - -class JdbcResultSet implements ResultSet, JdbcWrapper { - - // temporary calendar instance (per connection) used for normalizing the date and time - // even though the cfg is already in UTC format, JDBC 3.0 requires java.sql.Time to have its date - // removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed - // instead of dealing with longs, a Calendar object is used instead - private final Calendar defaultCalendar; - - private final JdbcStatement statement; - private final Cursor cursor; - private final Map nameToIndex = new LinkedHashMap<>(); - - private boolean closed = false; - private boolean wasNull = false; - - private int rowNumber; - - JdbcResultSet(JdbcConfiguration cfg, @Nullable JdbcStatement statement, Cursor cursor) { - this.statement = statement; - this.cursor = cursor; - // statement can be null so we have to extract the timeZone from the non-nullable cfg - // TODO: should we consider the locale as well? - this.defaultCalendar = Calendar.getInstance(cfg.timeZone(), Locale.ROOT); - - List columns = cursor.columns(); - for (int i = 0; i < columns.size(); i++) { - nameToIndex.put(columns.get(i).name, Integer.valueOf(i + 1)); - } - } - - private Object column(int columnIndex) throws SQLException { - checkOpen(); - if (columnIndex < 1 || columnIndex > cursor.columnSize()) { - throw new SQLException("Invalid column index [" + columnIndex + "]"); - } - Object object = null; - try { - object = cursor.column(columnIndex - 1); - } catch (IllegalArgumentException iae) { - throw new SQLException(iae.getMessage()); - } - wasNull = (object == null); - return object; - } - - private int column(String columnName) throws SQLException { - checkOpen(); - Integer index = nameToIndex.get(columnName); - if (index == null) { - throw new SQLException("Invalid column label [" + columnName + "]"); - } - return index.intValue(); - } - - void checkOpen() throws SQLException { - if (isClosed()) { - throw new SQLException("Closed result set"); - } - } - - @Override - public boolean next() throws SQLException { - checkOpen(); - if (cursor.next()) { - rowNumber++; - return true; - } - return false; - } - - @Override - public void close() throws SQLException { - if (!closed) { - closed = true; - if (statement != null) { - statement.resultSetWasClosed(); - } - cursor.close(); - } - } - - @Override - public boolean wasNull() throws SQLException { - checkOpen(); - return wasNull; - } - - @Override - public String getString(int columnIndex) throws SQLException { - Object val = column(columnIndex); - return val != null ? val.toString() : null; - } - - @Override - public boolean getBoolean(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? (Boolean) val : false; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a boolean", cce); - } - } - - @Override - public byte getByte(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).byteValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a byte", cce); - } - } - - @Override - public short getShort(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).shortValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a short", cce); - } - } - - @Override - public int getInt(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).intValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to an int", cce); - } - } - - @Override - public long getLong(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).longValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); - } - } - - @Override - public float getFloat(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).floatValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a float", cce); - } - } - - @Override - public double getDouble(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val != null ? ((Number) val).doubleValue() : 0; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a double", cce); - } - } - - @Override - public byte[] getBytes(int columnIndex) throws SQLException { - try { - return (byte[]) column(columnIndex); - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a byte array", cce); - } - } - - @Override - public Date getDate(int columnIndex) throws SQLException { - return getDate(columnIndex, null); - } - - @Override - public Time getTime(int columnIndex) throws SQLException { - return getTime(columnIndex, null); - } - - @Override - public Timestamp getTimestamp(int columnIndex) throws SQLException { - return getTimestamp(columnIndex, null); - } - - @Override - public String getString(String columnLabel) throws SQLException { - return getString(column(columnLabel)); - } - - @Override - public boolean getBoolean(String columnLabel) throws SQLException { - return getBoolean(column(columnLabel)); - } - - @Override - public byte getByte(String columnLabel) throws SQLException { - return getByte(column(columnLabel)); - } - - @Override - public short getShort(String columnLabel) throws SQLException { - return getShort(column(columnLabel)); - } - - @Override - public int getInt(String columnLabel) throws SQLException { - return getInt(column(columnLabel)); - } - - @Override - public long getLong(String columnLabel) throws SQLException { - return getLong(column(columnLabel)); - } - - @Override - public float getFloat(String columnLabel) throws SQLException { - return getFloat(column(columnLabel)); - } - - @Override - public double getDouble(String columnLabel) throws SQLException { - return getDouble(column(columnLabel)); - } - - @Override - public byte[] getBytes(String columnLabel) throws SQLException { - return getBytes(column(columnLabel)); - } - - @Override - public Date getDate(String columnLabel) throws SQLException { - return getDate(column(columnLabel)); - } - - private Long dateTime(int columnIndex) throws SQLException { - Object val = column(columnIndex); - try { - return val == null ? null : (Long) val; - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); - } - } - - private Calendar safeCalendar(Calendar calendar) { - return calendar == null ? defaultCalendar : calendar; - } - - @Override - public Date getDate(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertDate(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Date getDate(String columnLabel, Calendar cal) throws SQLException { - return getDate(column(columnLabel), cal); - } - - @Override - public Time getTime(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertTime(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Time getTime(String columnLabel) throws SQLException { - return getTime(column(columnLabel)); - } - - @Override - public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { - return TypeConverter.convertTimestamp(dateTime(columnIndex), safeCalendar(cal)); - } - - @Override - public Timestamp getTimestamp(String columnLabel) throws SQLException { - return getTimestamp(column(columnLabel)); - } - - @Override - public Time getTime(String columnLabel, Calendar cal) throws SQLException { - return getTime(column(columnLabel), cal); - } - - @Override - public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { - return getTimestamp(column(columnLabel), cal); - } - - @Override - public ResultSetMetaData getMetaData() throws SQLException { - return new JdbcResultSetMetaData(this, cursor.columns()); - } - - @Override - public Object getObject(int columnIndex) throws SQLException { - return convert(columnIndex, null); - } - - @Override - public T getObject(int columnIndex, Class type) throws SQLException { - if (type == null) { - throw new SQLException("type is null"); - } - - return getObject(columnIndex, type); - } - - private T convert(int columnIndex, Class type) throws SQLException { - checkOpen(); - if (columnIndex < 1 || columnIndex > cursor.columnSize()) { - throw new SQLException("Invalid column index [" + columnIndex + "]"); - } - - Object val = column(columnIndex); - - if (val == null) { - return null; - } - - JDBCType columnType = cursor.columns().get(columnIndex - 1).type; try { - return TypeConverter.convert(val, columnType, type); - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to " + type, cce); - } - } - - @Override - public Object getObject(int columnIndex, Map> map) throws SQLException { - if (map == null || map.isEmpty()) { - return getObject(columnIndex); - } - throw new SQLFeatureNotSupportedException("getObject with non-empty Map not supported"); - } - - @Override - public Object getObject(String columnLabel) throws SQLException { - return getObject(column(columnLabel)); - } - - @Override - public T getObject(String columnLabel, Class type) throws SQLException { - return getObject(column(columnLabel), type); - } - - @Override - public Object getObject(String columnLabel, Map> map) throws SQLException { - return getObject(column(columnLabel), map); - } - - @Override - public int findColumn(String columnLabel) throws SQLException { - return column(columnLabel); - } - - @Override - public boolean isBeforeFirst() throws SQLException { - return rowNumber == 0; - } - - @Override - public boolean isAfterLast() throws SQLException { - throw new SQLFeatureNotSupportedException("isAfterLast not supported"); - } - - @Override - public boolean isFirst() throws SQLException { - return rowNumber == 1; - } - - @Override - public boolean isLast() throws SQLException { - throw new SQLFeatureNotSupportedException("isLast not supported"); - } - - @Override - public int getRow() throws SQLException { - return rowNumber; - } - - @Override - public void setFetchSize(int rows) throws SQLException { - checkOpen(); - if (rows < 0) { - throw new SQLException("Rows is negative"); - } - if (rows != getFetchSize()) { - throw new SQLException("Fetch size cannot be changed"); - } - // ignore fetch size since scrolls cannot be changed in flight - } - - @Override - public int getFetchSize() throws SQLException { - /* - * Instead of returning the fetch size the user requested we make a - * stab at returning the fetch size that we actually used, returning - * the batch size of the current row. This allows us to assert these - * batch sizes in testing and lets us point users to something that - * they can use for debugging. - */ - checkOpen(); - return cursor.batchSize(); - } - - @Override - public Statement getStatement() throws SQLException { - checkOpen(); - return statement; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - @Deprecated - public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public InputStream getAsciiStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("AsciiStream not supported"); - } - - @Override - @Deprecated - public InputStream getUnicodeStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); - } - - @Override - public InputStream getBinaryStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("BinaryStream not supported"); - } - - @Override - @Deprecated - public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public InputStream getAsciiStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("AsciiStream not supported"); - } - - @Override - @Deprecated - public InputStream getUnicodeStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); - } - - @Override - public InputStream getBinaryStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("BinaryStream not supported"); - } - - @Override - public SQLWarning getWarnings() throws SQLException { - checkOpen(); - return null; - } - - @Override - public void clearWarnings() throws SQLException { - checkOpen(); - } - - @Override - public String getCursorName() throws SQLException { - throw new SQLFeatureNotSupportedException("Cursor name not supported"); - } - - @Override - public Reader getCharacterStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("CharacterStream not supported"); - } - - @Override - public Reader getCharacterStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("CharacterStream not supported"); - } - - @Override - public BigDecimal getBigDecimal(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public BigDecimal getBigDecimal(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("BigDecimal not supported"); - } - - @Override - public void beforeFirst() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public void afterLast() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean first() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean last() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean absolute(int row) throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean relative(int rows) throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public boolean previous() throws SQLException { - throw new SQLException("ResultSet is forward-only"); - } - - @Override - public int getType() throws SQLException { - checkOpen(); - return TYPE_FORWARD_ONLY; - } - - @Override - public int getConcurrency() throws SQLException { - checkOpen(); - return CONCUR_READ_ONLY; - } - - @Override - public void setFetchDirection(int direction) throws SQLException { - checkOpen(); - if (direction != FETCH_FORWARD) { - throw new SQLException("Fetch direction must be FETCH_FORWARD"); - } - } - - @Override - public int getFetchDirection() throws SQLException { - checkOpen(); - return FETCH_FORWARD; - } - - @Override - public boolean rowUpdated() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public boolean rowInserted() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public boolean rowDeleted() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNull(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBoolean(int columnIndex, boolean x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateByte(int columnIndex, byte x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateShort(int columnIndex, short x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateInt(int columnIndex, int x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateLong(int columnIndex, long x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateFloat(int columnIndex, float x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDouble(int columnIndex, double x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateString(int columnIndex, String x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBytes(int columnIndex, byte[] x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDate(int columnIndex, Date x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTime(int columnIndex, Time x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(int columnIndex, Object x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNull(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBoolean(String columnLabel, boolean x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateByte(String columnLabel, byte x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateShort(String columnLabel, short x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateInt(String columnLabel, int x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateLong(String columnLabel, long x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateFloat(String columnLabel, float x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDouble(String columnLabel, double x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateString(String columnLabel, String x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBytes(String columnLabel, byte[] x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateDate(String columnLabel, Date x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTime(String columnLabel, Time x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateObject(String columnLabel, Object x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void insertRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void deleteRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void cancelRowUpdates() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void moveToInsertRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void refreshRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void moveToCurrentRow() throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public Ref getRef(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Ref not supported"); - } - - @Override - public Blob getBlob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Blob not supported"); - } - - @Override - public Clob getClob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Clob not supported"); - } - - @Override - public Array getArray(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("Array not supported"); - } - - @Override - public Ref getRef(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Ref not supported"); - } - - @Override - public Blob getBlob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Blob not supported"); - } - - @Override - public Clob getClob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Clob not supported"); - } - - @Override - public Array getArray(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("Array not supported"); - } - - @Override - public URL getURL(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("URL not supported"); - } - - @Override - public URL getURL(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("URL not supported"); - } - - @Override - public void updateRef(int columnIndex, Ref x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRef(String columnLabel, Ref x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, Blob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, Blob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Clob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Clob x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateArray(int columnIndex, Array x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateArray(String columnLabel, Array x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public RowId getRowId(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("RowId not supported"); - } - - @Override - public RowId getRowId(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("RowId not supported"); - } - - @Override - public void updateRowId(int columnIndex, RowId x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateRowId(String columnLabel, RowId x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public int getHoldability() throws SQLException { - checkOpen(); - return HOLD_CURSORS_OVER_COMMIT; - } - - @Override - public void updateNString(int columnIndex, String nString) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNString(String columnLabel, String nString) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, NClob nClob) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, NClob nClob) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public NClob getNClob(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NClob not supported"); - } - - @Override - public NClob getNClob(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NClob not supported"); - } - - @Override - public SQLXML getSQLXML(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("SQLXML not supported"); - } - - @Override - public SQLXML getSQLXML(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("SQLXML not supported"); - } - - @Override - public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public String getNString(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NString not supported"); - } - - @Override - public String getNString(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NString not supported"); - } - - @Override - public Reader getNCharacterStream(int columnIndex) throws SQLException { - throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); - } - - @Override - public Reader getNCharacterStream(String columnLabel) throws SQLException { - throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); - } - - @Override - public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(int columnIndex, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateClob(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(int columnIndex, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public void updateNClob(String columnLabel, Reader reader) throws SQLException { - throw new SQLFeatureNotSupportedException("Writes not supported"); - } - - @Override - public String toString() { - return format(Locale.ROOT, "%s:row %d", getClass().getSimpleName(), rowNumber); - } -} +/* + * 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.xpack.sql.jdbc.net.client.Cursor; +import org.elasticsearch.xpack.sql.jdbc.net.protocol.ColumnInfo; +import org.elasticsearch.xpack.sql.jdbc.net.protocol.Nullable; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.lang.String.format; + +class JdbcResultSet implements ResultSet, JdbcWrapper { + + // temporary calendar instance (per connection) used for normalizing the date and time + // even though the cfg is already in UTC format, JDBC 3.0 requires java.sql.Time to have its date + // removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed + // instead of dealing with longs, a Calendar object is used instead + private final Calendar defaultCalendar; + + private final JdbcStatement statement; + private final Cursor cursor; + private final Map nameToIndex = new LinkedHashMap<>(); + + private boolean closed = false; + private boolean wasNull = false; + + private int rowNumber; + + JdbcResultSet(JdbcConfiguration cfg, @Nullable JdbcStatement statement, Cursor cursor) { + this.statement = statement; + this.cursor = cursor; + // statement can be null so we have to extract the timeZone from the non-nullable cfg + // TODO: should we consider the locale as well? + this.defaultCalendar = Calendar.getInstance(cfg.timeZone(), Locale.ROOT); + + List columns = cursor.columns(); + for (int i = 0; i < columns.size(); i++) { + nameToIndex.put(columns.get(i).name, Integer.valueOf(i + 1)); + } + } + + private Object column(int columnIndex) throws SQLException { + checkOpen(); + if (columnIndex < 1 || columnIndex > cursor.columnSize()) { + throw new SQLException("Invalid column index [" + columnIndex + "]"); + } + Object object = null; + try { + object = cursor.column(columnIndex - 1); + } catch (IllegalArgumentException iae) { + throw new SQLException(iae.getMessage()); + } + wasNull = (object == null); + return object; + } + + private int column(String columnName) throws SQLException { + checkOpen(); + Integer index = nameToIndex.get(columnName); + if (index == null) { + throw new SQLException("Invalid column label [" + columnName + "]"); + } + return index.intValue(); + } + + void checkOpen() throws SQLException { + if (isClosed()) { + throw new SQLException("Closed result set"); + } + } + + @Override + public boolean next() throws SQLException { + checkOpen(); + if (cursor.next()) { + rowNumber++; + return true; + } + return false; + } + + @Override + public void close() throws SQLException { + if (!closed) { + closed = true; + if (statement != null) { + statement.resultSetWasClosed(); + } + cursor.close(); + } + } + + @Override + public boolean wasNull() throws SQLException { + checkOpen(); + return wasNull; + } + + @Override + public String getString(int columnIndex) throws SQLException { + Object val = column(columnIndex); + return val != null ? val.toString() : null; + } + + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? (Boolean) val : false; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a boolean", cce); + } + } + + @Override + public byte getByte(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).byteValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a byte", cce); + } + } + + @Override + public short getShort(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).shortValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a short", cce); + } + } + + @Override + public int getInt(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).intValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to an int", cce); + } + } + + @Override + public long getLong(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).longValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); + } + } + + @Override + public float getFloat(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).floatValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a float", cce); + } + } + + @Override + public double getDouble(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val != null ? ((Number) val).doubleValue() : 0; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a double", cce); + } + } + + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + try { + return (byte[]) column(columnIndex); + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a byte array", cce); + } + } + + @Override + public Date getDate(int columnIndex) throws SQLException { + return getDate(columnIndex, null); + } + + @Override + public Time getTime(int columnIndex) throws SQLException { + return getTime(columnIndex, null); + } + + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + return getTimestamp(columnIndex, null); + } + + @Override + public String getString(String columnLabel) throws SQLException { + return getString(column(columnLabel)); + } + + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + return getBoolean(column(columnLabel)); + } + + @Override + public byte getByte(String columnLabel) throws SQLException { + return getByte(column(columnLabel)); + } + + @Override + public short getShort(String columnLabel) throws SQLException { + return getShort(column(columnLabel)); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + return getInt(column(columnLabel)); + } + + @Override + public long getLong(String columnLabel) throws SQLException { + return getLong(column(columnLabel)); + } + + @Override + public float getFloat(String columnLabel) throws SQLException { + return getFloat(column(columnLabel)); + } + + @Override + public double getDouble(String columnLabel) throws SQLException { + return getDouble(column(columnLabel)); + } + + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + return getBytes(column(columnLabel)); + } + + @Override + public Date getDate(String columnLabel) throws SQLException { + return getDate(column(columnLabel)); + } + + private Long dateTime(int columnIndex) throws SQLException { + Object val = column(columnIndex); + try { + return val == null ? null : (Long) val; + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to a long", cce); + } + } + + private Calendar safeCalendar(Calendar calendar) { + return calendar == null ? defaultCalendar : calendar; + } + + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertDate(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException { + return getDate(column(columnLabel), cal); + } + + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertTime(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Time getTime(String columnLabel) throws SQLException { + return getTime(column(columnLabel)); + } + + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { + return TypeConverter.convertTimestamp(dateTime(columnIndex), safeCalendar(cal)); + } + + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + return getTimestamp(column(columnLabel)); + } + + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException { + return getTime(column(columnLabel), cal); + } + + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { + return getTimestamp(column(columnLabel), cal); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return new JdbcResultSetMetaData(this, cursor.columns()); + } + + @Override + public Object getObject(int columnIndex) throws SQLException { + return convert(columnIndex, null); + } + + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + if (type == null) { + throw new SQLException("type is null"); + } + + return getObject(columnIndex, type); + } + + private T convert(int columnIndex, Class type) throws SQLException { + checkOpen(); + if (columnIndex < 1 || columnIndex > cursor.columnSize()) { + throw new SQLException("Invalid column index [" + columnIndex + "]"); + } + + Object val = column(columnIndex); + + if (val == null) { + return null; + } + + JDBCType columnType = cursor.columns().get(columnIndex - 1).type; + try { + return TypeConverter.convert(val, columnType, type); + } catch (ClassCastException cce) { + throw new SQLException("unable to convert column " + columnIndex + " to " + type, cce); + } + } + + @Override + public Object getObject(int columnIndex, Map> map) throws SQLException { + if (map == null || map.isEmpty()) { + return getObject(columnIndex); + } + throw new SQLFeatureNotSupportedException("getObject with non-empty Map not supported"); + } + + @Override + public Object getObject(String columnLabel) throws SQLException { + return getObject(column(columnLabel)); + } + + @Override + public T getObject(String columnLabel, Class type) throws SQLException { + return getObject(column(columnLabel), type); + } + + @Override + public Object getObject(String columnLabel, Map> map) throws SQLException { + return getObject(column(columnLabel), map); + } + + @Override + public int findColumn(String columnLabel) throws SQLException { + return column(columnLabel); + } + + @Override + public boolean isBeforeFirst() throws SQLException { + return rowNumber == 0; + } + + @Override + public boolean isAfterLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isAfterLast not supported"); + } + + @Override + public boolean isFirst() throws SQLException { + return rowNumber == 1; + } + + @Override + public boolean isLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isLast not supported"); + } + + @Override + public int getRow() throws SQLException { + return rowNumber; + } + + @Override + public void setFetchSize(int rows) throws SQLException { + checkOpen(); + if (rows < 0) { + throw new SQLException("Rows is negative"); + } + if (rows != getFetchSize()) { + throw new SQLException("Fetch size cannot be changed"); + } + // ignore fetch size since scrolls cannot be changed in flight + } + + @Override + public int getFetchSize() throws SQLException { + /* + * Instead of returning the fetch size the user requested we make a + * stab at returning the fetch size that we actually used, returning + * the batch size of the current row. This allows us to assert these + * batch sizes in testing and lets us point users to something that + * they can use for debugging. + */ + checkOpen(); + return cursor.batchSize(); + } + + @Override + public Statement getStatement() throws SQLException { + checkOpen(); + return statement; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + @Deprecated + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("AsciiStream not supported"); + } + + @Override + @Deprecated + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); + } + + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("BinaryStream not supported"); + } + + @Override + @Deprecated + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("AsciiStream not supported"); + } + + @Override + @Deprecated + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("UnicodeStream not supported"); + } + + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("BinaryStream not supported"); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + checkOpen(); + return null; + } + + @Override + public void clearWarnings() throws SQLException { + checkOpen(); + } + + @Override + public String getCursorName() throws SQLException { + throw new SQLFeatureNotSupportedException("Cursor name not supported"); + } + + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("CharacterStream not supported"); + } + + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("CharacterStream not supported"); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("BigDecimal not supported"); + } + + @Override + public void beforeFirst() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public void afterLast() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean first() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean last() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean absolute(int row) throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean relative(int rows) throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public boolean previous() throws SQLException { + throw new SQLException("ResultSet is forward-only"); + } + + @Override + public int getType() throws SQLException { + checkOpen(); + return TYPE_FORWARD_ONLY; + } + + @Override + public int getConcurrency() throws SQLException { + checkOpen(); + return CONCUR_READ_ONLY; + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + checkOpen(); + if (direction != FETCH_FORWARD) { + throw new SQLException("Fetch direction must be FETCH_FORWARD"); + } + } + + @Override + public int getFetchDirection() throws SQLException { + checkOpen(); + return FETCH_FORWARD; + } + + @Override + public boolean rowUpdated() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public boolean rowInserted() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public boolean rowDeleted() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNull(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateString(int columnIndex, String x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNull(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateString(String columnLabel, String x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void insertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void deleteRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void cancelRowUpdates() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void moveToInsertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void refreshRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void moveToCurrentRow() throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public Ref getRef(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Ref not supported"); + } + + @Override + public Blob getBlob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob not supported"); + } + + @Override + public Clob getClob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob not supported"); + } + + @Override + public Array getArray(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("Array not supported"); + } + + @Override + public Ref getRef(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Ref not supported"); + } + + @Override + public Blob getBlob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Blob not supported"); + } + + @Override + public Clob getClob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Clob not supported"); + } + + @Override + public Array getArray(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("Array not supported"); + } + + @Override + public URL getURL(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("URL not supported"); + } + + @Override + public URL getURL(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("URL not supported"); + } + + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public RowId getRowId(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("RowId not supported"); + } + + @Override + public RowId getRowId(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("RowId not supported"); + } + + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public int getHoldability() throws SQLException { + checkOpen(); + return HOLD_CURSORS_OVER_COMMIT; + } + + @Override + public void updateNString(int columnIndex, String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNString(String columnLabel, String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public NClob getNClob(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NClob not supported"); + } + + @Override + public NClob getNClob(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NClob not supported"); + } + + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("SQLXML not supported"); + } + + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("SQLXML not supported"); + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public String getNString(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NString not supported"); + } + + @Override + public String getNString(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NString not supported"); + } + + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); + } + + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("NCharacterStream not supported"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(int columnIndex, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateClob(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(int columnIndex, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public void updateNClob(String columnLabel, Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("Writes not supported"); + } + + @Override + public String toString() { + return format(Locale.ROOT, "%s:row %d", getClass().getSimpleName(), rowNumber); + } +} From 05504163ccaa3220c9ef46dc9e2773ffa50b3e43 Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Thu, 21 Jun 2018 09:54:31 +0300 Subject: [PATCH 6/8] Fixed `gradle check` complaints --- .../sql/jdbc/jdbc/JdbcPreparedStatement.java | 11 +++++++--- .../jdbc/jdbc/JdbcPreparedStatementTests.java | 20 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index b27e693d149e5..6929678a6194a 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java @@ -39,6 +39,7 @@ 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; @@ -370,14 +371,14 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale dateToSet = (java.util.Date) x; } else if (x instanceof LocalDateTime){ LocalDateTime ldt = (LocalDateTime) x; - Calendar cal = Calendar.getInstance(cfg.timeZone()); + 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 Date) { - dateToSet = TypeConverter.convertDate(((Date) x).getTime(), Calendar.getInstance(cfg.timeZone())); + dateToSet = TypeConverter.convertDate(((Date) x).getTime(), getDefaultCalendar()); } else { - dateToSet = TypeConverter.convertDate(((Time) x).getTime(), Calendar.getInstance(cfg.timeZone())); + dateToSet = TypeConverter.convertDate(((Time) x).getTime(), getDefaultCalendar()); } setParam(parameterIndex, dateToSet, Types.TIMESTAMP); @@ -411,6 +412,10 @@ private void checkKnownUnsupportedTypes(Object x) throws SQLFeatureNotSupportedE } } } + + private Calendar getDefaultCalendar() { + return Calendar.getInstance(cfg.timeZone(), Locale.ROOT); + } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { diff --git a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java index a9fbd7c4ba670..7cbd8f2bc7ceb 100644 --- a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java +++ b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java @@ -8,6 +8,7 @@ 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; @@ -15,6 +16,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.Clock; import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; @@ -318,7 +320,7 @@ public void testTimestamp() throws SQLException { assertEquals(someTimestamp.getTime(), ((Date)value(jps)).getTime()); assertEquals(TIMESTAMP, jdbcType(jps)); - Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + Calendar nonDefaultCal = randomCalendar(); // February 29th, 2016. 01:17:55 GMT = 1456708675000 millis since epoch jps.setTimestamp(1, new Timestamp(1456708675000L), nonDefaultCal); assertEquals(1456708675000L + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); @@ -340,7 +342,7 @@ public void testTime() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); Time time = new Time(4675000); - Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + Calendar nonDefaultCal = randomCalendar(); jps.setTime(1, time, nonDefaultCal); assertEquals(4675000 + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); assertEquals(TIMESTAMP, jdbcType(jps)); @@ -362,7 +364,7 @@ public void testSqlDate() throws SQLException { assertEquals(TIMESTAMP, jdbcType(jps)); someSqlDate = new java.sql.Date(randomMillisSinceEpoch()); - Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + Calendar nonDefaultCal = randomCalendar(); jps.setDate(1, someSqlDate, nonDefaultCal); assertEquals(someSqlDate.getTime() + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); assertEquals(TIMESTAMP, jdbcType(jps)); @@ -378,7 +380,7 @@ public void testSqlDate() throws SQLException { public void testCalendar() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); - Calendar someCalendar = Calendar.getInstance(); + Calendar someCalendar = randomCalendar(); someCalendar.setTimeInMillis(randomMillisSinceEpoch()); jps.setObject(1, someCalendar); @@ -392,7 +394,7 @@ public void testCalendar() throws SQLException { SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE)); assertEquals("Conversion from type " + someCalendar.getClass().getName() + " to DOUBLE not supported", sqle.getMessage()); - Calendar nonDefaultCal = Calendar.getInstance(randomTimeZone()); + Calendar nonDefaultCal = randomCalendar(); jps.setObject(1, nonDefaultCal); assertEquals(nonDefaultCal.getTime(), (Date) value(jps)); assertEquals(TIMESTAMP, jdbcType(jps)); @@ -416,7 +418,7 @@ public void testDate() throws SQLException { public void testLocalDateTime() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); - LocalDateTime ldt = LocalDateTime.now(); + LocalDateTime ldt = LocalDateTime.now(Clock.systemDefaultZone()); jps.setObject(1, ldt); assertEquals(Date.class, value(jps).getClass()); @@ -433,7 +435,7 @@ public void testLocalDateTime() throws SQLException { public void testBytesSetter() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); - byte[] buffer = "some data".getBytes(); + byte[] buffer = "some data".getBytes(StandardCharsets.UTF_8); jps.setBytes(1, buffer); assertEquals(byte[].class, value(jps).getClass()); assertEquals(VARBINARY, jdbcType(jps)); @@ -468,4 +470,8 @@ private JDBCType jdbcType(JdbcPreparedStatement jps) throws SQLException { private Object value(JdbcPreparedStatement jps) throws SQLException { return jps.query.getParam(1).value; } + + private Calendar randomCalendar() { + return Calendar.getInstance(randomTimeZone(), Locale.ROOT); + } } From 2e219074a4c7b5eb8345fdd3d9f689bde0fd814c Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Fri, 22 Jun 2018 11:22:28 +0300 Subject: [PATCH 7/8] Cosmetic changes to test methods. Changed the way dates using Calendars are converted before being sent to ES. --- .../sql/jdbc/jdbc/JdbcPreparedStatement.java | 66 ++++---- .../xpack/sql/jdbc/jdbc/TypeConverter.java | 13 +- .../jdbc/jdbc/JdbcPreparedStatementTests.java | 151 +++++++++++++++--- 3 files changed, 169 insertions(+), 61 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index 6929678a6194a..e1e81efb15339 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java @@ -23,6 +23,7 @@ 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; @@ -131,22 +132,22 @@ public void setString(int parameterIndex, String x) throws SQLException { @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { - setObject(parameterIndex, x); + setObject(parameterIndex, x, Types.VARBINARY); } @Override public void setDate(int parameterIndex, Date x) throws SQLException { - setObject(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); } @Override public void setTime(int parameterIndex, Time x) throws SQLException { - setObject(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); } @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { - setObject(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); } @Override @@ -229,40 +230,40 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { if (cal == null) { - setDate(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { + if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { return; } - - setDate(parameterIndex, new Date(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); + // 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 { if (cal == null) { - setTime(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { + if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { return; } - - setTime(parameterIndex, new Time(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); + // 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 { if (cal == null) { - setTimestamp(parameterIndex, x); + setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (checkNull(parameterIndex, x, Types.TIMESTAMP)) { + if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { return; } - - setTimestamp(parameterIndex, new Timestamp(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal))); + // converting to UTC since this is what ES is storing internally + setObject(parameterIndex, new Timestamp(TypeConverter.convertFromCalendarToUTC(x.getTime(), cal)), Types.TIMESTAMP); } @Override @@ -329,7 +330,7 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale // 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 SQLException(e.getMessage()); + throw new SQLDataException(e.getMessage()); } // set the null value on the type and exit @@ -346,20 +347,17 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale || x instanceof Long || x instanceof Float || x instanceof Double - || x instanceof String) { - try { - setParam(parameterIndex, - TypeConverter.convert(x, TypeConverter.fromJavaToJDBC(x.getClass()), DataType.fromJdbcTypeToJava(targetJDBCType)), - targetSqlType); - } catch (ClassCastException cce) { - throw new SQLException("Unable to convert " + x.getClass().getName() + " to " + targetJDBCType, cce); - } + || x instanceof String) + { + setParam(parameterIndex, + TypeConverter.convert(x, TypeConverter.fromJavaToJDBC(x.getClass()), DataType.fromJdbcTypeToJava(targetJDBCType)), + targetSqlType); } else if (x instanceof Timestamp || x instanceof Calendar - || x instanceof java.util.Date - || x instanceof LocalDateTime || x instanceof Date - || x instanceof Time) { + || 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; @@ -367,18 +365,18 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale dateToSet = new java.util.Date(((Timestamp) x).getTime()); } else if (x instanceof Calendar) { dateToSet = ((Calendar) x).getTime(); - } else if (x instanceof java.util.Date) { - dateToSet = (java.util.Date) x; + } 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 Date) { - dateToSet = TypeConverter.convertDate(((Date) x).getTime(), getDefaultCalendar()); + } else if (x instanceof Time) { + dateToSet = new java.util.Date(((Time) x).getTime()); } else { - dateToSet = TypeConverter.convertDate(((Time) x).getTime(), getDefaultCalendar()); + dateToSet = (java.util.Date) x; } setParam(parameterIndex, dateToSet, Types.TIMESTAMP); @@ -517,7 +515,7 @@ public long executeLargeUpdate() throws SQLException { throw new SQLFeatureNotSupportedException("Batching not supported"); } - private boolean checkNull(int parameterIndex, Object o, int type) throws SQLException { + private boolean setIfNull(int parameterIndex, Object o, int type) throws SQLException { if (o == null) { setNull(parameterIndex, type); return true; diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java index b38d372e26414..66e145b7ae1d8 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java @@ -10,6 +10,7 @@ import java.sql.Date; import java.sql.JDBCType; +import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Time; @@ -124,23 +125,27 @@ static long convertFromCalendarToUTC(long value, Calendar cal) { c.setTimeInMillis(value); ZonedDateTime convertedDateTime = ZonedDateTime - .ofInstant(c.toInstant(), ZoneOffset.ofTotalSeconds(c.getTimeZone().getRawOffset() / 1000)) + .ofInstant(c.toInstant(), c.getTimeZone().toZoneId()) .withZoneSameLocal(ZoneOffset.UTC); - return convertedDateTime.toEpochSecond() * 1000 + convertedDateTime.getNano() / 1_000_000; + return convertedDateTime.toInstant().toEpochMilli(); } /** * Converts object val from columnType to type */ @SuppressWarnings("unchecked") - static T convert(Object val, JDBCType columnType, Class type) throws SQLException, ClassCastException { + static T convert(Object val, JDBCType columnType, Class type) throws SQLException { if (type == null) { return (T) convert(val, columnType); } if (type.isInstance(val)) { - return type.cast(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) { diff --git a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java index 7cbd8f2bc7ceb..ad96825896e1a 100644 --- a/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java +++ b/x-pack/plugin/sql/jdbc/src/test/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatementTests.java @@ -18,6 +18,8 @@ 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; @@ -32,12 +34,12 @@ 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; -import static java.sql.JDBCType.VARBINARY;; public class JdbcPreparedStatementTests extends ESTestCase { - public void testBooleanSetters() throws SQLException { + public void testSettingBooleanValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); jps.setBoolean(1, true); @@ -51,6 +53,7 @@ public void testBooleanSetters() throws SQLException { 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)); @@ -59,12 +62,16 @@ public void testBooleanSetters() throws SQLException { 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 testStringSetters() throws SQLException { + public void testSettingStringValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); jps.setString(1, "foo bar"); @@ -78,12 +85,17 @@ public void testStringSetters() throws SQLException { 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 testByteSetters() throws SQLException { + public void testSettingByteTypeValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); jps.setByte(1, (byte) 6); @@ -93,6 +105,7 @@ public void testByteSetters() throws SQLException { 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)); @@ -109,12 +122,16 @@ public void testByteSetters() throws SQLException { 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 testShortSetters() throws SQLException { + public void testSettingShortTypeValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); short someShort = randomShort(); @@ -125,6 +142,7 @@ public void testShortSetters() throws SQLException { 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)); @@ -137,6 +155,10 @@ public void testShortSetters() throws SQLException { 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()); @@ -145,7 +167,7 @@ public void testShortSetters() throws SQLException { assertEquals("Numeric " + 256 + " out of range", sqle.getMessage()); } - public void testIntegerSetters() throws SQLException { + public void testSettingIntegerValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); int someInt = randomInt(); @@ -156,6 +178,7 @@ public void testIntegerSetters() throws SQLException { 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)); @@ -165,6 +188,11 @@ public void testIntegerSetters() throws SQLException { 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()); @@ -177,7 +205,7 @@ public void testIntegerSetters() throws SQLException { assertEquals("Numeric " + randomIntNotShort + " out of range", sqle.getMessage()); } - public void testLongSetters() throws SQLException { + public void testSettingLongValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); long someLong = randomLong(); @@ -188,6 +216,7 @@ public void testLongSetters() throws SQLException { 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)); @@ -200,6 +229,11 @@ public void testLongSetters() throws SQLException { 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()); @@ -212,7 +246,7 @@ public void testLongSetters() throws SQLException { assertEquals("Numeric " + randomLongNotShort + " out of range", sqle.getMessage()); } - public void testFloatSetters() throws SQLException { + public void testSettingFloatValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); float someFloat = randomFloat(); @@ -223,6 +257,7 @@ public void testFloatSetters() throws SQLException { 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)); @@ -235,6 +270,11 @@ public void testFloatSetters() throws SQLException { 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()); @@ -249,7 +289,7 @@ public void testFloatSetters() throws SQLException { Long.toString(Math.round(floatNotInt.doubleValue()))), sqle.getMessage()); } - public void testDoubleSetters() throws SQLException { + public void testSettingDoubleValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); double someDouble = randomDouble(); @@ -260,6 +300,7 @@ public void testDoubleSetters() throws SQLException { 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)); @@ -268,6 +309,11 @@ public void testDoubleSetters() throws SQLException { 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()); @@ -312,7 +358,7 @@ public Object[] getAttributes() throws SQLException { assertEquals("Unsupported JDBC type [CHAR]", iae.getMessage()); } - public void testTimestamp() throws SQLException { + public void testSettingTimestampValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch()); @@ -323,39 +369,51 @@ public void testTimestamp() throws SQLException { Calendar nonDefaultCal = randomCalendar(); // February 29th, 2016. 01:17:55 GMT = 1456708675000 millis since epoch jps.setTimestamp(1, new Timestamp(1456708675000L), nonDefaultCal); - assertEquals(1456708675000L + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + assertEquals(1456708675000L, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal)); assertEquals(TIMESTAMP, jdbcType(jps)); long beforeEpochTime = -randomMillisSinceEpoch(); jps.setTimestamp(1, new Timestamp(beforeEpochTime), nonDefaultCal); - assertEquals(beforeEpochTime + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + 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 testTime() throws SQLException { + public void testSettingTimeValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); Time time = new Time(4675000); Calendar nonDefaultCal = randomCalendar(); jps.setTime(1, time, nonDefaultCal); - assertEquals(4675000 + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + 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 testSqlDate() throws SQLException { + public void testSettingSqlDateValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); java.sql.Date someSqlDate = new java.sql.Date(randomMillisSinceEpoch()); @@ -366,19 +424,25 @@ public void testSqlDate() throws SQLException { someSqlDate = new java.sql.Date(randomMillisSinceEpoch()); Calendar nonDefaultCal = randomCalendar(); jps.setDate(1, someSqlDate, nonDefaultCal); - assertEquals(someSqlDate.getTime() + nonDefaultCal.getTimeZone().getRawOffset(), ((Date)value(jps)).getTime()); + 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 testCalendar() throws SQLException { + public void testSettingCalendarValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); Calendar someCalendar = randomCalendar(); someCalendar.setTimeInMillis(randomMillisSinceEpoch()); @@ -386,53 +450,71 @@ public void testCalendar() throws SQLException { 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)); - SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someCalendar, Types.DOUBLE)); - assertEquals("Conversion from type " + someCalendar.getClass().getName() + " to DOUBLE not supported", sqle.getMessage()); - Calendar nonDefaultCal = randomCalendar(); jps.setObject(1, nonDefaultCal); assertEquals(nonDefaultCal.getTime(), (Date) value(jps)); assertEquals(TIMESTAMP, jdbcType(jps)); } - public void testDate() throws SQLException { + 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 testLocalDateTime() throws SQLException { + 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 testBytesSetter() throws SQLException { + public void testSettingByteArrayValues() throws SQLException { JdbcPreparedStatement jps = createJdbcPreparedStatement(); byte[] buffer = "some data".getBytes(StandardCharsets.UTF_8); @@ -443,6 +525,7 @@ public void testBytesSetter() throws SQLException { 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); @@ -454,6 +537,17 @@ public void testBytesSetter() throws SQLException { 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()); @@ -474,4 +568,15 @@ private Object value(JdbcPreparedStatement jps) throws SQLException { 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(); + } } From 04136cfec6d385eeaf6bfe2e4ae159ab19a219fb Mon Sep 17 00:00:00 2001 From: Andrei Stefan Date: Tue, 26 Jun 2018 14:14:53 +0300 Subject: [PATCH 8/8] Readability improvements --- .../sql/jdbc/jdbc/JdbcPreparedStatement.java | 78 ++++++++++--------- .../xpack/sql/jdbc/jdbc/JdbcResultSet.java | 7 +- .../xpack/sql/jdbc/jdbc/TypeConverter.java | 17 ++-- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java index e1e81efb15339..bae4260ac2b69 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcPreparedStatement.java @@ -233,7 +233,8 @@ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLExceptio setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { + if (x == null) { + setNull(parameterIndex, Types.TIMESTAMP); return; } // converting to UTC since this is what ES is storing internally @@ -246,7 +247,8 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { + if (x == null) { + setNull(parameterIndex, Types.TIMESTAMP); return; } // converting to UTC since this is what ES is storing internally @@ -259,7 +261,8 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S setObject(parameterIndex, x, Types.TIMESTAMP); return; } - if (setIfNull(parameterIndex, x, Types.TIMESTAMP)) { + if (x == null) { + setNull(parameterIndex, Types.TIMESTAMP); return; } // converting to UTC since this is what ES is storing internally @@ -320,7 +323,7 @@ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLE public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { setObject(parameterIndex, xmlObject); } - + @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { checkOpen(); @@ -340,25 +343,23 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale } checkKnownUnsupportedTypes(x); - 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); - } else if (x instanceof Timestamp + 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 ) { + || 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) { @@ -380,23 +381,32 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale } setParam(parameterIndex, dateToSet, Types.TIMESTAMP); + return; } else if (targetJDBCType == JDBCType.VARCHAR) { setParam(parameterIndex, String.valueOf(x), Types.VARCHAR); - } else { - // 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"); - } - } else if (x instanceof byte[]) { - if (targetJDBCType != JDBCType.VARBINARY) { - throw new SQLFeatureNotSupportedException( - "Conversion from type byte[] to " + targetJDBCType + " not supported"); + return; } - setParam(parameterIndex, x, Types.VARBINARY); - } else { + // 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 { @@ -514,12 +524,4 @@ public boolean execute(String sql, String[] columnNames) throws SQLException { public long executeLargeUpdate() throws SQLException { throw new SQLFeatureNotSupportedException("Batching not supported"); } - - private boolean setIfNull(int parameterIndex, Object o, int type) throws SQLException { - if (o == null) { - setNull(parameterIndex, type); - return true; - } - return false; - } } \ No newline at end of file diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java index 5736dcb25224d..351ac73a88f28 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcResultSet.java @@ -360,11 +360,8 @@ private T convert(int columnIndex, Class type) throws SQLException { } JDBCType columnType = cursor.columns().get(columnIndex - 1).type; - try { - return TypeConverter.convert(val, columnType, type); - } catch (ClassCastException cce) { - throw new SQLException("unable to convert column " + columnIndex + " to " + type, cce); - } + + return TypeConverter.convert(val, columnType, type); } @Override diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java index 66e145b7ae1d8..1e24a03c8b31c 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/TypeConverter.java @@ -24,9 +24,11 @@ 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; @@ -58,7 +60,7 @@ private TypeConverter() { private static final Map, JDBCType> javaToJDBC; static { - javaToJDBC = Arrays.stream(DataType.values()) + Map, JDBCType> aMap = Arrays.stream(DataType.values()) .filter(dataType -> dataType.javaClass() != null && dataType != DataType.HALF_FLOAT && dataType != DataType.SCALED_FLOAT @@ -66,9 +68,10 @@ private TypeConverter() { .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 - javaToJDBC.put(Calendar.class, JDBCType.TIMESTAMP); - javaToJDBC.put(java.util.Date.class, JDBCType.TIMESTAMP); - javaToJDBC.put(LocalDateTime.class, JDBCType.TIMESTAMP); + aMap.put(Calendar.class, JDBCType.TIMESTAMP); + aMap.put(java.util.Date.class, JDBCType.TIMESTAMP); + aMap.put(LocalDateTime.class, JDBCType.TIMESTAMP); + javaToJDBC = Collections.unmodifiableMap(aMap); } /** @@ -276,10 +279,10 @@ static boolean isSigned(JDBCType jdbcType) throws SQLException { static JDBCType fromJavaToJDBC(Class clazz) throws SQLException { - for (Class key : javaToJDBC.keySet()) { + for (Entry, 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 (key.isAssignableFrom(clazz)) { - return javaToJDBC.get(key); + if (e.getKey().isAssignableFrom(clazz)) { + return e.getValue(); } }