From bc7d5bd6205b23c99d01d2895ffb5c48ba423ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 9 Jun 2020 16:15:59 +0200 Subject: [PATCH] fix: ResultSet#get(...) methods should auto convert values (#143) * fix: ResultSet#get(...) methods should auto convert values * tests: add additional tests * tests: add more conversion tests * tests: add more timestamp tests * tests: add timestamp tests * review: process review comments --- .../spanner/jdbc/AbstractJdbcWrapper.java | 122 ++++ .../cloud/spanner/jdbc/JdbcResultSet.java | 443 ++++++++++--- .../cloud/spanner/jdbc/JdbcTypeConverter.java | 19 + .../spanner/jdbc/AbstractJdbcWrapperTest.java | 134 ++++ .../cloud/spanner/jdbc/JdbcGrpcErrorTest.java | 2 +- .../cloud/spanner/jdbc/JdbcResultSetTest.java | 605 +++++++++++++++++- .../cloud/spanner/jdbc/JdbcStatementTest.java | 2 + .../spanner/jdbc/JdbcTypeConverterTest.java | 45 ++ .../jdbc/SpannerJdbcExceptionMatcher.java | 6 +- 9 files changed, 1272 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java index abfa0f58..aef5638e 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java @@ -22,9 +22,11 @@ import java.sql.Date; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.sql.Wrapper; +import java.util.Calendar; /** Base class for all Cloud Spanner JDBC classes that implement the {@link Wrapper} interface. */ abstract class AbstractJdbcWrapper implements Wrapper { @@ -147,6 +149,126 @@ static float checkedCastToFloat(double val) throws SQLException { return (float) val; } + /** + * Parses the given string value as a long. Throws {@link SQLException} if the string is not a + * valid long value. + */ + static long parseLong(String val) throws SQLException { + Preconditions.checkNotNull(val); + try { + return Long.valueOf(val); + } catch (NumberFormatException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a double. Throws {@link SQLException} if the string is not a + * valid double value. + */ + static double parseDouble(String val) throws SQLException { + Preconditions.checkNotNull(val); + try { + return Double.valueOf(val); + } catch (NumberFormatException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a {@link Date} value. Throws {@link SQLException} if the + * string is not a valid {@link Date} value. + */ + static Date parseDate(String val) throws SQLException { + Preconditions.checkNotNull(val); + try { + return JdbcTypeConverter.toSqlDate(com.google.cloud.Date.parseDate(val)); + } catch (IllegalArgumentException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid date", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a {@link Date} value in the timezone of the given {@link + * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Date} value. + */ + static Date parseDate(String val, Calendar cal) throws SQLException { + Preconditions.checkNotNull(val); + Preconditions.checkNotNull(cal); + try { + return JdbcTypeConverter.toSqlDate(com.google.cloud.Date.parseDate(val), cal); + } catch (IllegalArgumentException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid date", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a {@link Time} value. Throws {@link SQLException} if the + * string is not a valid {@link Time} value. + */ + static Time parseTime(String val) throws SQLException { + Preconditions.checkNotNull(val); + try { + return Time.valueOf(val); + } catch (IllegalArgumentException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid time", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a {@link Time} value in the timezone of the given {@link + * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Time} value. + */ + static Time parseTime(String val, Calendar cal) throws SQLException { + Preconditions.checkNotNull(val); + Preconditions.checkNotNull(cal); + try { + return JdbcTypeConverter.parseSqlTime(val, cal); + } catch (IllegalArgumentException e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid time", val), com.google.rpc.Code.INVALID_ARGUMENT, e); + } + } + + /** + * Parses the given string value as a {@link Timestamp} value. Throws {@link SQLException} if the + * string is not a valid {@link Timestamp} value. + */ + static Timestamp parseTimestamp(String val) throws SQLException { + Preconditions.checkNotNull(val); + try { + return JdbcTypeConverter.toSqlTimestamp(com.google.cloud.Timestamp.parseTimestamp(val)); + } catch (Exception e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid timestamp", val), + com.google.rpc.Code.INVALID_ARGUMENT, + e); + } + } + + /** + * Parses the given string value as a {@link Timestamp} value in the timezone of the given {@link + * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Timestamp} value. + */ + static Timestamp parseTimestamp(String val, Calendar cal) throws SQLException { + Preconditions.checkNotNull(val); + Preconditions.checkNotNull(cal); + try { + return JdbcTypeConverter.setTimestampInCalendar( + com.google.cloud.Timestamp.parseTimestamp(val).toSqlTimestamp(), cal); + } catch (Exception e) { + throw JdbcSqlExceptionFactory.of( + String.format("%s is not a valid timestamp", val), + com.google.rpc.Code.INVALID_ARGUMENT, + e); + } + } + /** Should return true if this object has been closed */ public abstract boolean isClosed() throws SQLException; diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java index e9532554..2e4014ba 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java @@ -115,61 +115,219 @@ private boolean isNull(int columnIndex) { return wasNull; } - private boolean isNull(String columnName) { - wasNull = spanner.isNull(columnName); - return wasNull; + SQLException createInvalidToGetAs(String sqlType, Code type) { + return JdbcSqlExceptionFactory.of( + String.format("Invalid column type to get as %s: %s", sqlType, type.name()), + com.google.rpc.Code.INVALID_ARGUMENT); } @Override public String getString(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) ? null : spanner.getString(columnIndex - 1); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? null : String.valueOf(spanner.getBoolean(spannerIndex)); + case BYTES: + return isNull ? null : spanner.getBytes(spannerIndex).toBase64(); + case DATE: + return isNull ? null : spanner.getDate(spannerIndex).toString(); + case FLOAT64: + return isNull ? null : Double.toString(spanner.getDouble(spannerIndex)); + case INT64: + return isNull ? null : Long.toString(spanner.getLong(spannerIndex)); + case STRING: + return isNull ? null : spanner.getString(spannerIndex); + case TIMESTAMP: + return isNull ? null : spanner.getTimestamp(spannerIndex).toString(); + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("string", type); + } } @Override public boolean getBoolean(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) ? false : spanner.getBoolean(columnIndex - 1); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? false : spanner.getBoolean(spannerIndex); + case FLOAT64: + return isNull ? false : spanner.getDouble(spannerIndex) != 0D; + case INT64: + return isNull ? false : spanner.getLong(spannerIndex) != 0L; + case STRING: + return isNull ? false : Boolean.valueOf(spanner.getString(spannerIndex)); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("boolean", type); + } } @Override public byte getByte(int columnIndex) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1); - return checkedCastToByte(val); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? (byte) 0 : (spanner.getBoolean(spannerIndex) ? (byte) 1 : 0); + case FLOAT64: + return isNull + ? (byte) 0 + : checkedCastToByte(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); + case INT64: + return isNull ? (byte) 0 : checkedCastToByte(spanner.getLong(spannerIndex)); + case STRING: + return isNull ? (byte) 0 : checkedCastToByte(parseLong(spanner.getString(spannerIndex))); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("byte", type); + } } @Override public short getShort(int columnIndex) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1); - return checkedCastToShort(val); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (short) 1 : 0); + case FLOAT64: + return isNull + ? 0 + : checkedCastToShort(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); + case INT64: + return isNull ? 0 : checkedCastToShort(spanner.getLong(spannerIndex)); + case STRING: + return isNull ? 0 : checkedCastToShort(parseLong(spanner.getString(spannerIndex))); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("short", type); + } } @Override public int getInt(int columnIndex) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1); - return checkedCastToInt(val); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? 1 : 0); + case FLOAT64: + return isNull + ? 0 + : checkedCastToInt(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); + case INT64: + return isNull ? 0 : checkedCastToInt(spanner.getLong(spannerIndex)); + case STRING: + return isNull ? 0 : checkedCastToInt(parseLong(spanner.getString(spannerIndex))); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("int", type); + } } @Override public long getLong(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? 0L : (spanner.getBoolean(spannerIndex) ? 1L : 0L); + case FLOAT64: + return isNull ? 0L : Double.valueOf(spanner.getDouble(spannerIndex)).longValue(); + case INT64: + return isNull ? 0L : spanner.getLong(spannerIndex); + case STRING: + return isNull ? 0L : parseLong(spanner.getString(spannerIndex)); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("long", type); + } } @Override public float getFloat(int columnIndex) throws SQLException { checkClosedAndValidRow(); - double val = isNull(columnIndex) ? 0D : spanner.getDouble(columnIndex - 1); - return checkedCastToFloat(val); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (float) 1 : 0); + case FLOAT64: + return isNull ? 0 : checkedCastToFloat(spanner.getDouble(spannerIndex)); + case INT64: + return isNull ? 0 : checkedCastToFloat(spanner.getLong(spannerIndex)); + case STRING: + return isNull ? 0 : checkedCastToFloat(parseDouble(spanner.getString(spannerIndex))); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("float", type); + } } @Override public double getDouble(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) ? 0D : spanner.getDouble(columnIndex - 1); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case BOOL: + return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (double) 1 : 0); + case FLOAT64: + return isNull ? 0 : spanner.getDouble(spannerIndex); + case INT64: + return isNull ? 0 : spanner.getLong(spannerIndex); + case STRING: + return isNull ? 0 : parseDouble(spanner.getString(spannerIndex)); + case BYTES: + case DATE: + case STRUCT: + case TIMESTAMP: + case ARRAY: + default: + throw createInvalidToGetAs("double", type); + } } @Override @@ -181,25 +339,74 @@ public byte[] getBytes(int columnIndex) throws SQLException { @Override public Date getDate(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.toSqlDate(spanner.getDate(columnIndex - 1)); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case DATE: + return isNull ? null : JdbcTypeConverter.toSqlDate(spanner.getDate(spannerIndex)); + case STRING: + return isNull ? null : parseDate(spanner.getString(spannerIndex)); + case TIMESTAMP: + return isNull + ? null + : new Date(spanner.getTimestamp(spannerIndex).toSqlTimestamp().getTime()); + case BOOL: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("date", type); + } } @Override public Time getTime(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnIndex - 1)); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case STRING: + return isNull ? null : parseTime(spanner.getString(spannerIndex)); + case TIMESTAMP: + return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex)); + case BOOL: + case DATE: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("time", type); + } } @Override public Timestamp getTimestamp(int columnIndex) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(columnIndex - 1)); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case DATE: + return isNull ? null : JdbcTypeConverter.toSqlTimestamp(spanner.getDate(spannerIndex)); + case STRING: + return isNull ? null : parseTimestamp(spanner.getString(spannerIndex)); + case TIMESTAMP: + return isNull ? null : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(spannerIndex)); + case BOOL: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("timestamp", type); + } } private InputStream getInputStream(String val, Charset charset) { @@ -230,102 +437,92 @@ public InputStream getBinaryStream(int columnIndex) throws SQLException { @Override public String getString(String columnLabel) throws SQLException { - checkClosedAndValidRow(); - return isNull(columnLabel) ? null : spanner.getString(columnLabel); + return getString(findColumn(columnLabel)); } @Override public boolean getBoolean(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) ? false : spanner.getBoolean(columnLabel); + return getBoolean(findColumn(columnLabel)); } @Override public byte getByte(String columnLabel) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel); - return checkedCastToByte(val); + return getByte(findColumn(columnLabel)); } @Override public short getShort(String columnLabel) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel); - return checkedCastToShort(val); + return getShort(findColumn(columnLabel)); } @Override public int getInt(String columnLabel) throws SQLException { checkClosedAndValidRow(); - long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel); - return checkedCastToInt(val); + return getInt(findColumn(columnLabel)); } @Override public long getLong(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) ? 0L : spanner.getLong(columnLabel); + return getLong(findColumn(columnLabel)); } @Override public float getFloat(String columnLabel) throws SQLException { checkClosedAndValidRow(); - double val = isNull(columnLabel) ? 0D : spanner.getDouble(columnLabel); - return checkedCastToFloat(val); + return getFloat(findColumn(columnLabel)); } @Override public double getDouble(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) ? 0D : spanner.getDouble(columnLabel); + return getDouble(findColumn(columnLabel)); } @Override public byte[] getBytes(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) ? null : spanner.getBytes(columnLabel).toByteArray(); + return getBytes(findColumn(columnLabel)); } @Override public Date getDate(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) ? null : JdbcTypeConverter.toSqlDate(spanner.getDate(columnLabel)); + return getDate(findColumn(columnLabel)); } @Override public Time getTime(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) - ? null - : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnLabel)); + return getTime(findColumn(columnLabel)); } @Override public Timestamp getTimestamp(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return isNull(columnLabel) - ? null - : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(columnLabel)); + return getTimestamp(findColumn(columnLabel)); } @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return getInputStream(getString(columnLabel), StandardCharsets.US_ASCII); + return getAsciiStream(findColumn(columnLabel)); } @Override @Deprecated public InputStream getUnicodeStream(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return getInputStream(getString(columnLabel), StandardCharsets.UTF_16LE); + return getUnicodeStream(findColumn(columnLabel)); } @Override public InputStream getBinaryStream(String columnLabel) throws SQLException { checkClosedAndValidRow(); - byte[] val = getBytes(columnLabel); - return val == null ? null : new ByteArrayInputStream(val); + return getBinaryStream(findColumn(columnLabel)); } @Override @@ -342,8 +539,7 @@ public JdbcResultSetMetaData getMetaData() throws SQLException { @Override public Object getObject(String columnLabel) throws SQLException { checkClosedAndValidRow(); - Type type = spanner.getColumnType(columnLabel); - return isNull(columnLabel) ? null : getObject(type, columnLabel); + return getObject(findColumn(columnLabel)); } @Override @@ -353,10 +549,6 @@ public Object getObject(int columnIndex) throws SQLException { return isNull(columnIndex) ? null : getObject(type, columnIndex); } - private Object getObject(Type type, String columnLabel) throws SQLException { - return getObject(type, spanner.getColumnIndex(columnLabel) + 1); - } - private Object getObject(Type type, int columnIndex) throws SQLException { if (type == Type.bool()) return getBoolean(columnIndex); if (type == Type.bytes()) return getBytes(columnIndex); @@ -390,9 +582,7 @@ public Reader getCharacterStream(int columnIndex) throws SQLException { @Override public Reader getCharacterStream(String columnLabel) throws SQLException { - checkClosedAndValidRow(); - String val = getString(columnLabel); - return val == null ? null : new StringReader(val); + return getCharacterStream(findColumn(columnLabel)); } @Override @@ -404,7 +594,7 @@ public BigDecimal getBigDecimal(int columnIndex) throws SQLException { @Override public BigDecimal getBigDecimal(String columnLabel) throws SQLException { checkClosedAndValidRow(); - return getBigDecimal(spanner.getColumnIndex(columnLabel) + 1, false, 0); + return getBigDecimal(findColumn(columnLabel), false, 0); } @Override @@ -418,36 +608,48 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException @Deprecated public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { checkClosedAndValidRow(); - return getBigDecimal(spanner.getColumnIndex(columnLabel) + 1, true, scale); + return getBigDecimal(findColumn(columnLabel), true, scale); } private BigDecimal getBigDecimal(int columnIndex, boolean fixedScale, int scale) throws SQLException { - Type type = spanner.getColumnType(columnIndex - 1); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + boolean isNull = isNull(columnIndex); BigDecimal res; - if (isNull(columnIndex)) { - res = null; - } else { - if (type.getCode() == Code.STRING) { + switch (type) { + case BOOL: + res = + isNull + ? null + : (spanner.getBoolean(columnIndex - 1) ? BigDecimal.ONE : BigDecimal.ZERO); + break; + case FLOAT64: + res = isNull ? null : BigDecimal.valueOf(spanner.getDouble(spannerIndex)); + break; + case INT64: + res = isNull ? null : BigDecimal.valueOf(spanner.getLong(spannerIndex)); + break; + case STRING: try { - res = new BigDecimal(spanner.getString(columnIndex - 1)); + res = isNull ? null : new BigDecimal(spanner.getString(spannerIndex)); + break; } catch (NumberFormatException e) { throw JdbcSqlExceptionFactory.of( "The column does not contain a valid BigDecimal", com.google.rpc.Code.INVALID_ARGUMENT, e); } - } else if (type.getCode() == Code.INT64) { - res = BigDecimal.valueOf(spanner.getLong(columnIndex - 1)); - } else if (type.getCode() == Code.FLOAT64) { - res = BigDecimal.valueOf(spanner.getDouble(columnIndex - 1)); - } else { - throw JdbcSqlExceptionFactory.of( - "The column does not contain a valid BigDecimal", com.google.rpc.Code.INVALID_ARGUMENT); - } - if (fixedScale) { - res = res.setScale(scale, RoundingMode.HALF_UP); - } + case BYTES: + case DATE: + case TIMESTAMP: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("BigDecimal", type); + } + if (res != null && fixedScale) { + res = res.setScale(scale, RoundingMode.HALF_UP); } return res; } @@ -506,56 +708,99 @@ public Array getArray(int columnIndex) throws SQLException { @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.toSqlDate(spanner.getDate(columnIndex - 1), cal); + if (isNull(columnIndex)) { + return null; + } + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case DATE: + return JdbcTypeConverter.toSqlDate(spanner.getDate(spannerIndex), cal); + case STRING: + return parseDate(spanner.getString(spannerIndex), cal); + case TIMESTAMP: + return new Date( + JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal).getTime()); + case BOOL: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("date", type); + } } @Override public Date getDate(String columnLabel, Calendar cal) throws SQLException { - checkClosedAndValidRow(); - return isNull(columnLabel) - ? null - : JdbcTypeConverter.toSqlDate(spanner.getDate(columnLabel), cal); + return getDate(findColumn(columnLabel), cal); } @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnIndex - 1), cal); + boolean isNull = isNull(columnIndex); + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case STRING: + return isNull ? null : parseTime(spanner.getString(spannerIndex), cal); + case TIMESTAMP: + return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex), cal); + case BOOL: + case DATE: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("time", type); + } } @Override public Time getTime(String columnLabel, Calendar cal) throws SQLException { - checkClosedAndValidRow(); - return isNull(columnLabel) - ? null - : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnLabel), cal); + return getTime(findColumn(columnLabel), cal); } @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { checkClosedAndValidRow(); - return isNull(columnIndex) - ? null - : JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(columnIndex - 1), cal); + if (isNull(columnIndex)) { + return null; + } + int spannerIndex = columnIndex - 1; + Code type = spanner.getColumnType(spannerIndex).getCode(); + switch (type) { + case DATE: + return JdbcTypeConverter.toSqlTimestamp(spanner.getDate(spannerIndex), cal); + case STRING: + return parseTimestamp(spanner.getString(spannerIndex), cal); + case TIMESTAMP: + return JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal); + case BOOL: + case FLOAT64: + case INT64: + case BYTES: + case STRUCT: + case ARRAY: + default: + throw createInvalidToGetAs("timestamp", type); + } } @Override public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { - checkClosedAndValidRow(); - return isNull(columnLabel) - ? null - : JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(columnLabel), cal); + return getTimestamp(findColumn(columnLabel), cal); } @Override public URL getURL(int columnIndex) throws SQLException { checkClosedAndValidRow(); try { - return isNull(columnIndex) ? null : new URL(spanner.getString(columnIndex - 1)); + return isNull(columnIndex) ? null : new URL(getString(columnIndex)); } catch (MalformedURLException e) { throw JdbcSqlExceptionFactory.of( "Invalid URL: " + spanner.getString(columnIndex - 1), diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java index a11cb5e8..e922e585 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java @@ -224,6 +224,14 @@ static java.sql.Timestamp toSqlTimestamp(Timestamp ts) { return ts == null ? null : ts.toSqlTimestamp(); } + static java.sql.Timestamp toSqlTimestamp(Date date) { + return date == null ? null : new java.sql.Timestamp(toSqlDate(date).getTime()); + } + + static java.sql.Timestamp toSqlTimestamp(Date date, Calendar cal) { + return date == null ? null : new java.sql.Timestamp(toSqlDate(date, cal).getTime()); + } + static java.sql.Timestamp getAsSqlTimestamp(Timestamp ts, Calendar cal) { return ts == null ? null : getTimestampInCalendar(ts.toSqlTimestamp(), cal); } @@ -334,6 +342,17 @@ static Time toSqlTime(Timestamp ts, Calendar cal) { return null; } + @SuppressWarnings("deprecation") + static java.sql.Time parseSqlTime(String val, Calendar cal) { + if (val != null) { + Time time = Time.valueOf(val); + cal.set(1970, 0, 1, time.getHours(), time.getMinutes(), time.getSeconds()); + cal.clear(Calendar.MILLISECOND); + return new java.sql.Time(cal.getTimeInMillis()); + } + return null; + } + static List toGoogleBytes(byte[][] bytes) { List res = new ArrayList<>(bytes.length); for (int index = 0; index < bytes.length; index++) { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java index 1a391dfb..593466f7 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java @@ -17,8 +17,15 @@ package com.google.cloud.spanner.jdbc; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import com.google.cloud.Timestamp; +import com.google.rpc.Code; +import java.sql.Date; import java.sql.SQLException; +import java.sql.Time; +import java.util.Calendar; +import java.util.TimeZone; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -176,4 +183,131 @@ private boolean unwrapSucceeds(AbstractJdbcWrapper subject, Class iface) { return false; } } + + @Test + public void testParseDouble() throws SQLException { + assertThat(AbstractJdbcWrapper.parseDouble("3.14")).isEqualTo(3.14D); + try { + AbstractJdbcWrapper.parseDouble("not a number"); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testParseDate() throws SQLException { + assertThat(AbstractJdbcWrapper.parseDate("2020-06-01")).isEqualTo(new Date(2020 - 1900, 5, 1)); + try { + AbstractJdbcWrapper.parseDate("01-06-2020"); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @Test + public void testParseDateWithCalendar() throws SQLException { + assertThat( + AbstractJdbcWrapper.parseDate( + "2020-06-01", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")))) + .isEqualTo( + new Date( + Timestamp.parseTimestamp("2020-06-01T00:00:00-07:00").toSqlTimestamp().getTime())); + assertThat( + AbstractJdbcWrapper.parseDate( + "2020-06-01", Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam")))) + .isEqualTo( + new Date( + Timestamp.parseTimestamp("2020-06-01T00:00:00+02:00").toSqlTimestamp().getTime())); + try { + AbstractJdbcWrapper.parseDate("01-06-2020", Calendar.getInstance()); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testParseTime() throws SQLException { + assertThat(AbstractJdbcWrapper.parseTime("10:31:05")).isEqualTo(new Time(10, 31, 5)); + try { + AbstractJdbcWrapper.parseTime("10.31.05"); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @Test + public void testParseTimeWithCalendar() throws SQLException { + assertThat( + AbstractJdbcWrapper.parseTime( + "10:31:05", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")))) + .isEqualTo( + new Time( + Timestamp.parseTimestamp("1970-01-01T10:31:05-08:00").toSqlTimestamp().getTime())); + assertThat( + AbstractJdbcWrapper.parseTime( + "10:31:05", Calendar.getInstance(TimeZone.getTimeZone("Pacific/Auckland")))) + .isEqualTo( + new Time( + Timestamp.parseTimestamp("1970-01-01T10:31:05+12:00").toSqlTimestamp().getTime())); + try { + AbstractJdbcWrapper.parseTime("10.31.05", Calendar.getInstance()); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @Test + public void testParseTimestamp() throws SQLException { + assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31:05Z")) + .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05Z").toSqlTimestamp()); + assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31:05.123Z")) + .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05.123Z").toSqlTimestamp()); + assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31Z")) + .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:00Z").toSqlTimestamp()); + assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31")) + .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:00Z").toSqlTimestamp()); + assertThat(AbstractJdbcWrapper.parseTimestamp("1970-01-01T00:00:00Z")) + .isEqualTo(Timestamp.ofTimeMicroseconds(0L).toSqlTimestamp()); + try { + AbstractJdbcWrapper.parseTimestamp("2020-06-01T10"); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } + + @Test + public void testParseTimestampWithCalendar() throws SQLException { + assertThat( + AbstractJdbcWrapper.parseTimestamp( + "2020-02-01T10:31:05Z", + Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")))) + .isEqualTo(Timestamp.parseTimestamp("2020-02-01T10:31:05-08:00").toSqlTimestamp()); + assertThat( + AbstractJdbcWrapper.parseTimestamp( + "2020-06-01T10:31:05Z", + Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam")))) + .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05+02:00").toSqlTimestamp()); + try { + AbstractJdbcWrapper.parseTimestamp( + "2020-06-01T10", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + } } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java index 41672132..1f886750 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java @@ -29,6 +29,7 @@ import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl; import com.google.protobuf.ListValue; import com.google.protobuf.Value; +import com.google.rpc.Code; import com.google.spanner.v1.ResultSetMetadata; import com.google.spanner.v1.StructType; import com.google.spanner.v1.StructType.Field; @@ -36,7 +37,6 @@ import com.google.spanner.v1.TypeCode; import io.grpc.Server; import io.grpc.Status; -import io.grpc.Status.Code; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import java.io.IOException; import java.net.InetSocketAddress; diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java index 44eefa97..9429288d 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java @@ -35,7 +35,7 @@ import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.StructField; import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl; -import io.grpc.Status.Code; +import com.google.rpc.Code; import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -112,6 +112,19 @@ public class JdbcResultSetTest { private static final int URL_COLINDEX_NULL = 19; private static final int URL_COLINDEX_NOTNULL = 20; + private static final String STRING_COL_NUMBER = "STRING_COL_NUMBER"; + private static final int STRING_COLINDEX_NUMBER = 21; + private static final String STRING_NUMBER_VALUE = "123"; + private static final String STRING_COL_DATE = "STRING_COL_DATE"; + private static final int STRING_COLINDEX_DATE = 22; + private static final String STRING_DATE_VALUE = "2020-06-01"; + private static final String STRING_COL_TIMESTAMP = "STRING_COL_TIMESTAMP"; + private static final int STRING_COLINDEX_TIMESTAMP = 23; + private static final String STRING_TIMESTAMP_VALUE = "2020-06-01T10:31:15.123Z"; + private static final String STRING_COL_TIME = "STRING_COL_TIME"; + private static final int STRING_COLINDEX_TIME = 24; + private static final String STRING_TIME_VALUE = "10:31:15"; + private JdbcResultSet subject; static ResultSet getMockResultSet() { @@ -136,7 +149,11 @@ static ResultSet getMockResultSet() { StructField.of(ARRAY_COL_NULL, Type.array(Type.int64())), StructField.of(ARRAY_COL_NOT_NULL, Type.array(Type.int64())), StructField.of(URL_COL_NULL, Type.string()), - StructField.of(URL_COL_NOT_NULL, Type.string())), + StructField.of(URL_COL_NOT_NULL, Type.string()), + StructField.of(STRING_COL_NUMBER, Type.string()), + StructField.of(STRING_COL_DATE, Type.string()), + StructField.of(STRING_COL_TIMESTAMP, Type.string()), + StructField.of(STRING_COL_TIME, Type.string())), Arrays.asList( Struct.newBuilder() .set(STRING_COL_NULL) @@ -179,6 +196,14 @@ static ResultSet getMockResultSet() { .to((String) null) .set(URL_COL_NOT_NULL) .to(URL_VALUE) + .set(STRING_COL_NUMBER) + .to(STRING_NUMBER_VALUE) + .set(STRING_COL_DATE) + .to(STRING_DATE_VALUE) + .set(STRING_COL_TIMESTAMP) + .to(STRING_TIMESTAMP_VALUE) + .set(STRING_COL_TIME) + .to(STRING_TIME_VALUE) .build())); } @@ -242,6 +267,84 @@ public void testGetStringIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetStringIndexForBool() throws SQLException { + assertNotNull(subject.getString(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(String.valueOf(BOOLEAN_VALUE), subject.getString(BOOLEAN_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForInt64() throws SQLException { + assertNotNull(subject.getString(LONG_COLINDEX_NOTNULL)); + assertEquals(String.valueOf(LONG_VALUE), subject.getString(LONG_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForFloat64() throws SQLException { + assertNotNull(subject.getString(DOUBLE_COLINDEX_NOTNULL)); + assertEquals(String.valueOf(DOUBLE_VALUE), subject.getString(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForBytes() throws SQLException { + assertNotNull(subject.getString(BYTES_COLINDEX_NOTNULL)); + assertEquals(BYTES_VALUE.toBase64(), subject.getString(BYTES_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(BYTES_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForDate() throws SQLException { + assertNotNull(subject.getString(DATE_COLINDEX_NOTNULL)); + assertEquals(String.valueOf(DATE_VALUE), subject.getString(DATE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(DATE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForTimestamp() throws SQLException { + assertNotNull(subject.getString(TIMESTAMP_COLINDEX_NOTNULL)); + assertEquals(String.valueOf(TIMESTAMP_VALUE), subject.getString(TIMESTAMP_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getString(TIMESTAMP_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetStringIndexForArray() throws SQLException { + try { + subject.getString(ARRAY_COLINDEX_NOTNULL); + fail("missing SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + JdbcSqlException jse = (JdbcSqlException) e; + assertEquals(jse.getCode(), Code.INVALID_ARGUMENT); + } + } + + @Test + public void testGetStringIndexForNullArray() throws SQLException { + try { + subject.getString(ARRAY_COLINDEX_NULL); + fail("missing SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + JdbcSqlException jse = (JdbcSqlException) e; + assertEquals(jse.getCode(), Code.INVALID_ARGUMENT); + } + } + @Test public void testGetNStringIndex() throws SQLException { assertNotNull(subject.getNString(STRING_COLINDEX_NOTNULL)); @@ -284,15 +387,120 @@ public void testGetBooleanIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetBooleanIndexForLong() throws SQLException { + assertNotNull(subject.getBoolean(LONG_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertFalse(subject.getBoolean(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetBooleanIndexForDouble() throws SQLException { + assertNotNull(subject.getBoolean(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertFalse(subject.getBoolean(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetBooleanIndexForString() throws SQLException { + assertNotNull(subject.getBoolean(STRING_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertFalse(subject.getBoolean(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetBooleanIndexForDate() throws SQLException { + try { + subject.getBoolean(DATE_COLINDEX_NOTNULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + try { + subject.getBoolean(DATE_COLINDEX_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + + @Test + public void testGetNullBooleanIndex() throws SQLException { + assertFalse(subject.getBoolean(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertFalse(subject.getBoolean(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertFalse(subject.getBoolean(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + @Test public void testGetLongIndex() throws SQLException { assertNotNull(subject.getLong(LONG_COLINDEX_NOTNULL)); assertEquals(LONG_VALUE, subject.getLong(LONG_COLINDEX_NOTNULL)); assertFalse(subject.wasNull()); - assertEquals(0l, subject.getLong(LONG_COLINDEX_NULL)); + assertEquals(0L, subject.getLong(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetLongIndexForBool() throws SQLException { + assertNotNull(subject.getLong(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1L : 0L, subject.getLong(BOOLEAN_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertEquals(0L, subject.getLong(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetLongIndexForFloat64() throws SQLException { + assertNotNull(subject.getLong(DOUBLE_COLINDEX_NOTNULL)); + assertEquals((long) DOUBLE_VALUE, subject.getLong(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertEquals(0L, subject.getLong(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetLongIndexForString() throws SQLException { + try { + subject.getLong(STRING_COLINDEX_NOTNULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT); + } + } + + @Test + public void testGetLongIndexForNumberString() throws SQLException { + assertEquals( + Long.valueOf(STRING_NUMBER_VALUE).longValue(), subject.getLong(STRING_COLINDEX_NUMBER)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetLongIndexForNullString() throws SQLException { + assertEquals(0L, subject.getLong(STRING_COLINDEX_NULL)); assertTrue(subject.wasNull()); } + @Test + public void testGetLongIndexForTimestamp() throws SQLException { + try { + subject.getLong(TIMESTAMP_COLINDEX_NOTNULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT); + } + } + @Test public void testGetDoubleIndex() throws SQLException { assertNotNull(subject.getDouble(DOUBLE_COLINDEX_NOTNULL)); @@ -302,6 +510,47 @@ public void testGetDoubleIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetDoubleIndexFromString() throws SQLException { + assertNotNull(subject.getDouble(STRING_COLINDEX_NUMBER)); + assertEquals( + Double.valueOf(STRING_NUMBER_VALUE).doubleValue(), + subject.getDouble(STRING_COLINDEX_NUMBER), + 0d); + assertFalse(subject.wasNull()); + assertEquals(0d, subject.getDouble(STRING_COLINDEX_NULL), 0d); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetDoubleIndexFromBool() throws SQLException { + assertNotNull(subject.getDouble(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1d : 0d, subject.getDouble(BOOLEAN_COLINDEX_NOTNULL), 0d); + assertFalse(subject.wasNull()); + assertEquals(0d, subject.getDouble(BOOLEAN_COLINDEX_NULL), 0d); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetDoubleIndexFromInt64() throws SQLException { + assertNotNull(subject.getDouble(LONG_COLINDEX_NOTNULL)); + assertEquals(LONG_VALUE, subject.getDouble(LONG_COLINDEX_NOTNULL), 0d); + assertFalse(subject.wasNull()); + assertEquals(0d, subject.getDouble(LONG_COLINDEX_NULL), 0d); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetDoubleIndexFromTimestamp() throws SQLException { + try { + subject.getDouble(TIMESTAMP_COLINDEX_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT); + } + } + @SuppressWarnings("deprecation") @Test public void testGetBigDecimalIndexAndScale() throws SQLException { @@ -336,6 +585,37 @@ public void testGetDateIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetDateIndexFromString() throws SQLException { + assertNotNull(subject.getDate(STRING_COLINDEX_DATE)); + assertEquals(java.sql.Date.valueOf(STRING_DATE_VALUE), subject.getDate(STRING_COLINDEX_DATE)); + assertFalse(subject.wasNull()); + assertNull(subject.getDate(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetDateIndexFromTimestamp() throws SQLException { + assertNotNull(subject.getDate(TIMESTAMP_COLINDEX_NOTNULL)); + assertEquals( + new java.sql.Date(TIMESTAMP_VALUE.toSqlTimestamp().getTime()), + subject.getDate(TIMESTAMP_COL_NOT_NULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getDate(TIMESTAMP_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetDateIndexFromInt64() { + try { + subject.getDate(LONG_COLINDEX_NOTNULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + @Test public void testGetTimeIndex() throws SQLException { assertNotNull(subject.getTime(TIME_COLINDEX_NOTNULL)); @@ -346,6 +626,15 @@ public void testGetTimeIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetTimeIndexFromString() throws SQLException { + assertNotNull(subject.getTime(STRING_COLINDEX_TIME)); + assertEquals(Time.valueOf(STRING_TIME_VALUE), subject.getTime(STRING_COLINDEX_TIME)); + assertFalse(subject.wasNull()); + assertNull(subject.getTime(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + @Test public void testGetTimestampIndex() throws SQLException { assertNotNull(subject.getTimestamp(TIMESTAMP_COLINDEX_NOTNULL)); @@ -356,6 +645,36 @@ public void testGetTimestampIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetTimestampIndexFromString() throws SQLException { + assertNotNull(subject.getTimestamp(STRING_COLINDEX_TIMESTAMP)); + assertEquals( + Timestamp.parseTimestamp(STRING_TIMESTAMP_VALUE).toSqlTimestamp(), + subject.getTimestamp(STRING_COLINDEX_TIMESTAMP)); + assertFalse(subject.wasNull()); + assertNull(subject.getTimestamp(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetTimestampIndexFromDate() throws SQLException { + assertNotNull(subject.getTimestamp(DATE_COLINDEX_NOTNULL)); + assertEquals( + new java.sql.Timestamp( + DATE_VALUE.getYear() - 1900, + DATE_VALUE.getMonth() - 1, + DATE_VALUE.getDayOfMonth(), + 0, + 0, + 0, + 0), + subject.getTimestamp(DATE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + assertNull(subject.getTimestamp(DATE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + @Test public void testGetStringLabel() throws SQLException { assertNotNull(subject.getString(STRING_COL_NOT_NULL)); @@ -591,6 +910,33 @@ public void testGetTimestampIndexCalendar() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetTimestampIndexCalendarFromString() throws SQLException { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); + + assertNotNull(subject.getTimestamp(STRING_COLINDEX_TIMESTAMP, cal)); + assertEquals( + Timestamp.parseTimestamp(STRING_TIMESTAMP_VALUE.replace("Z", "-07:00")).toSqlTimestamp(), + subject.getTimestamp(STRING_COLINDEX_TIMESTAMP, cal)); + assertFalse(subject.wasNull()); + assertNull(subject.getTimestamp(STRING_COLINDEX_NULL, cal)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetTimestampIndexCalendarFromDate() throws SQLException { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); + + assertNotNull(subject.getTimestamp(DATE_COLINDEX_NOTNULL, cal)); + assertEquals( + Timestamp.parseTimestamp(String.format("%sT00:00:00-08:00", DATE_VALUE.toString())) + .toSqlTimestamp(), + subject.getTimestamp(DATE_COLINDEX_NOTNULL, cal)); + assertFalse(subject.wasNull()); + assertNull(subject.getTimestamp(DATE_COLINDEX_NULL, cal)); + assertTrue(subject.wasNull()); + } + @Test public void testGetTimestampLabelCalendar() throws SQLException { Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); @@ -621,6 +967,47 @@ public void testGetByteIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetByteIndexFromString() throws SQLException { + assertNotNull(subject.getByte(STRING_COLINDEX_NUMBER)); + assertEquals( + Byte.valueOf(STRING_NUMBER_VALUE).byteValue(), subject.getByte(STRING_COLINDEX_NUMBER)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetByteIndexFromDouble() throws SQLException { + assertNotNull(subject.getByte(DOUBLE_COLINDEX_NOTNULL)); + assertEquals((byte) DOUBLE_VALUE, subject.getByte(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetByteIndexFromBoolean() throws SQLException { + assertNotNull(subject.getByte(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getByte(BOOLEAN_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetNullByteIndex() throws SQLException { + assertEquals(0, subject.getByte(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getByte(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getByte(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getByte(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + try { + subject.getByte(TIMESTAMP_COL_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + @Test public void testGetShortIndex() throws SQLException { assertNotNull(subject.getShort(LONG_COLINDEX_NOTNULL)); @@ -630,6 +1017,58 @@ public void testGetShortIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetShortIndexFromString() throws SQLException { + assertNotNull(subject.getShort(STRING_COLINDEX_NUMBER)); + assertEquals( + Short.valueOf(STRING_NUMBER_VALUE).shortValue(), subject.getShort(STRING_COLINDEX_NUMBER)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetShortIndexFromDouble() throws SQLException { + assertNotNull(subject.getShort(DOUBLE_COLINDEX_NOTNULL)); + assertEquals((short) DOUBLE_VALUE, subject.getShort(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetShortIndexFromBoolean() throws SQLException { + assertNotNull(subject.getShort(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getShort(BOOLEAN_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetShortIndexFromBytes() throws SQLException { + try { + subject.getShort(BYTES_COL_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + + @Test + public void testGetNullShortIndex() throws SQLException { + assertEquals(0, subject.getShort(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getShort(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getShort(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getShort(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + try { + subject.getShort(TIMESTAMP_COL_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + @Test public void testGetIntIndex() throws SQLException { assertNotNull(subject.getInt(LONG_COLINDEX_NOTNULL)); @@ -640,6 +1079,58 @@ public void testGetIntIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetIntIndexFromString() throws SQLException { + assertNotNull(subject.getInt(STRING_COLINDEX_NUMBER)); + assertEquals( + Integer.valueOf(STRING_NUMBER_VALUE).intValue(), subject.getInt(STRING_COLINDEX_NUMBER)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetIntIndexFromDouble() throws SQLException { + assertNotNull(subject.getInt(DOUBLE_COLINDEX_NOTNULL)); + assertEquals((int) DOUBLE_VALUE, subject.getInt(DOUBLE_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetIntIndexFromBoolean() throws SQLException { + assertNotNull(subject.getInt(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getInt(BOOLEAN_COLINDEX_NOTNULL)); + assertFalse(subject.wasNull()); + } + + @Test + public void testGetIntIndexFromTimestamp() throws SQLException { + try { + subject.getInt(TIMESTAMP_COL_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + + @Test + public void testGetNullIntIndex() throws SQLException { + assertEquals(0, subject.getInt(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getInt(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getInt(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + assertEquals(0, subject.getInt(STRING_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + try { + subject.getInt(TIMESTAMP_COL_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode()); + } + } + @Test public void testGetFloatIndex() throws SQLException { assertNotNull(subject.getFloat(DOUBLE_COLINDEX_NOTNULL)); @@ -650,6 +1141,47 @@ public void testGetFloatIndex() throws SQLException { assertTrue(subject.wasNull()); } + @Test + public void testGetFloatIndexFromString() throws SQLException { + assertNotNull(subject.getFloat(STRING_COLINDEX_NUMBER)); + assertEquals( + Float.valueOf(STRING_NUMBER_VALUE).floatValue(), + subject.getFloat(STRING_COLINDEX_NUMBER), + 0f); + assertFalse(subject.wasNull()); + assertEquals(0f, subject.getFloat(STRING_COLINDEX_NULL), 0f); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetFloatIndexFromBool() throws SQLException { + assertNotNull(subject.getFloat(BOOLEAN_COLINDEX_NOTNULL)); + assertEquals(BOOLEAN_VALUE ? 1f : 0f, subject.getFloat(BOOLEAN_COLINDEX_NOTNULL), 0f); + assertFalse(subject.wasNull()); + assertEquals(0f, subject.getFloat(BOOLEAN_COLINDEX_NULL), 0f); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetFloatIndexFromInt64() throws SQLException { + assertNotNull(subject.getFloat(LONG_COLINDEX_NOTNULL)); + assertEquals(LONG_VALUE, subject.getFloat(LONG_COLINDEX_NOTNULL), 0f); + assertFalse(subject.wasNull()); + assertEquals(0f, subject.getFloat(LONG_COLINDEX_NULL), 0f); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetFloatIndexFromTimestamp() throws SQLException { + try { + subject.getFloat(TIMESTAMP_COLINDEX_NULL); + fail("missing expected SQLException"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT); + } + } + @Test public void testGetByteLabel() throws SQLException { assertNotNull(subject.getByte(LONG_COL_NOT_NULL)); @@ -858,6 +1390,73 @@ public void testGetAsciiStreamIndex() throws SQLException, IOException { assertTrue(subject.wasNull()); } + @Test + public void testGetAsciiStreamIndexForBool() throws SQLException, IOException { + assertNotNull(subject.getAsciiStream(BOOLEAN_COLINDEX_NOTNULL)); + InputStream actual = subject.getAsciiStream(BOOLEAN_COLINDEX_NOTNULL); + byte[] cbuf = new byte[10]; + int len = actual.read(cbuf, 0, cbuf.length); + assertEquals( + String.valueOf(BOOLEAN_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII)); + assertEquals(4, len); + assertFalse(subject.wasNull()); + assertNull(subject.getAsciiStream(BOOLEAN_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetAsciiStreamIndexForInt64() throws SQLException, IOException { + assertNotNull(subject.getAsciiStream(LONG_COLINDEX_NOTNULL)); + InputStream actual = subject.getAsciiStream(LONG_COLINDEX_NOTNULL); + byte[] cbuf = new byte[10]; + int len = actual.read(cbuf, 0, cbuf.length); + assertEquals(String.valueOf(LONG_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII)); + assertEquals(1, len); + assertFalse(subject.wasNull()); + assertNull(subject.getAsciiStream(LONG_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetAsciiStreamIndexForFloat64() throws SQLException, IOException { + assertNotNull(subject.getAsciiStream(DOUBLE_COLINDEX_NOTNULL)); + InputStream actual = subject.getAsciiStream(DOUBLE_COLINDEX_NOTNULL); + byte[] cbuf = new byte[20]; + int len = actual.read(cbuf, 0, cbuf.length); + assertEquals(String.valueOf(DOUBLE_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII)); + assertEquals(13, len); + assertFalse(subject.wasNull()); + assertNull(subject.getAsciiStream(DOUBLE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetAsciiStreamIndexForDate() throws SQLException, IOException { + assertNotNull(subject.getAsciiStream(DATE_COLINDEX_NOTNULL)); + InputStream actual = subject.getAsciiStream(DATE_COLINDEX_NOTNULL); + byte[] cbuf = new byte[10]; + int len = actual.read(cbuf, 0, cbuf.length); + assertEquals(String.valueOf(DATE_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII)); + assertEquals(10, len); + assertFalse(subject.wasNull()); + assertNull(subject.getAsciiStream(DATE_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + + @Test + public void testGetAsciiStreamIndexForTimestamp() throws SQLException, IOException { + assertNotNull(subject.getAsciiStream(TIMESTAMP_COLINDEX_NOTNULL)); + InputStream actual = subject.getAsciiStream(TIMESTAMP_COLINDEX_NOTNULL); + byte[] cbuf = new byte[100]; + int len = actual.read(cbuf, 0, cbuf.length); + assertEquals( + String.valueOf(TIMESTAMP_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII)); + assertEquals(30, len); + assertFalse(subject.wasNull()); + assertNull(subject.getAsciiStream(TIMESTAMP_COLINDEX_NULL)); + assertTrue(subject.wasNull()); + } + @SuppressWarnings("deprecation") @Test public void testGetUnicodeStreamIndex() throws SQLException, IOException { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java index c1b1e992..7349bfce 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java @@ -25,6 +25,7 @@ import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.Type; import com.google.cloud.spanner.connection.Connection; import com.google.cloud.spanner.connection.StatementParser; import com.google.cloud.spanner.connection.StatementResult; @@ -57,6 +58,7 @@ private JdbcStatement createStatement() { com.google.cloud.spanner.ResultSet resultSet = mock(com.google.cloud.spanner.ResultSet.class); when(resultSet.next()).thenReturn(true, false); + when(resultSet.getColumnType(0)).thenReturn(Type.int64()); when(resultSet.getLong(0)).thenReturn(1L); StatementResult selectResult = mock(StatementResult.class); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java index 870711d0..58ef03a1 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java @@ -760,4 +760,49 @@ public void testToJavaByteArrays() { List output = toJavaByteArrays(input); assertThat(Arrays.deepEquals(expected.toArray(), output.toArray())).isTrue(); } + + @SuppressWarnings("deprecation") + @Test + public void testDateToSqlTimestamp() { + assertThat(JdbcTypeConverter.toSqlTimestamp(com.google.cloud.Date.fromYearMonthDay(2020, 6, 1))) + .isEqualTo(new Timestamp(2020 - 1900, 5, 1, 0, 0, 0, 0)); + } + + @Test + public void testDateToSqlTimestampWithCalendar() { + assertThat( + JdbcTypeConverter.toSqlTimestamp( + com.google.cloud.Date.fromYearMonthDay(2020, 6, 1), + Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")))) + .isEqualTo( + com.google.cloud.Timestamp.parseTimestamp("2020-06-01T00:00:00-07:00") + .toSqlTimestamp()); + assertThat( + JdbcTypeConverter.toSqlTimestamp( + com.google.cloud.Date.fromYearMonthDay(2020, 6, 1), + Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam")))) + .isEqualTo( + com.google.cloud.Timestamp.parseTimestamp("2020-06-01T00:00:00+02:00") + .toSqlTimestamp()); + } + + @Test + public void testParseSqlTimeWithCalendar() { + assertThat( + JdbcTypeConverter.parseSqlTime( + "10:31:15", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")))) + .isEqualTo( + new Time( + com.google.cloud.Timestamp.parseTimestamp("1970-01-01T10:31:15-08:00") + .toSqlTimestamp() + .getTime())); + assertThat( + JdbcTypeConverter.parseSqlTime( + "10:31:15", Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam")))) + .isEqualTo( + new Time( + com.google.cloud.Timestamp.parseTimestamp("1970-01-01T10:31:15+01:00") + .toSqlTimestamp() + .getTime())); + } } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java b/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java index 5a1663c8..4cd5e9b9 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java @@ -17,7 +17,7 @@ package com.google.cloud.spanner.jdbc; import com.google.common.base.Preconditions; -import io.grpc.Status.Code; +import com.google.rpc.Code; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; @@ -52,9 +52,9 @@ public boolean matches(Object item) { if (exceptionClass.isAssignableFrom(item.getClass())) { JdbcSqlException exception = (JdbcSqlException) item; if (message == null) { - return exception.getErrorCode() == errorCode.value(); + return exception.getErrorCode() == errorCode.getNumber(); } - return exception.getErrorCode() == errorCode.value() + return exception.getErrorCode() == errorCode.getNumber() && exception.getMessage().endsWith(": " + message); } return false;