From 457924980ab0f10fcbb61a0cf1442069f4d0b8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 4 Sep 2020 15:10:29 +0200 Subject: [PATCH] feat: add support for NUMERIC type (#185) * feat: add support for NUMERIC type * deps: no snapshot * fix: add accidentally removed test script * fix: remove spanner version --- .../jdbc/AbstractJdbcPreparedStatement.java | 2 +- .../spanner/jdbc/AbstractJdbcWrapper.java | 58 ++++++++++++ .../cloud/spanner/jdbc/JdbcDataType.java | 27 ++++++ .../spanner/jdbc/JdbcDatabaseMetaData.java | 38 ++++++++ .../spanner/jdbc/JdbcParameterMetaData.java | 2 +- .../spanner/jdbc/JdbcParameterStore.java | 34 +++---- .../cloud/spanner/jdbc/JdbcResultSet.java | 26 ++++++ .../spanner/jdbc/JdbcResultSetMetaData.java | 8 +- .../cloud/spanner/jdbc/JdbcTypeConverter.java | 19 ++++ .../jdbc/DatabaseMetaData_GetColumns.sql | 3 + .../cloud/spanner/jdbc/JdbcArrayTest.java | 8 ++ .../jdbc/JdbcDatabaseMetaDataTest.java | 2 + .../cloud/spanner/jdbc/JdbcGrpcErrorTest.java | 18 ++-- .../spanner/jdbc/JdbcParameterStoreTest.java | 41 ++++++--- .../jdbc/JdbcPreparedStatementTest.java | 10 --- .../spanner/jdbc/JdbcTypeConverterTest.java | 40 ++++++++- .../jdbc/SpannerJdbcExceptionMatcher.java | 2 +- .../jdbc/it/ITJdbcDatabaseMetaDataTest.java | 4 +- .../jdbc/it/ITJdbcPreparedStatementTest.java | 89 +++++++++++++++++++ .../spanner/jdbc/it/CreateMusicTables.sql | 4 +- 20 files changed, 381 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java index 52ac32c9..bbd5975b 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java @@ -128,7 +128,7 @@ public void setDouble(int parameterIndex, double value) throws SQLException { @Override public void setBigDecimal(int parameterIndex, BigDecimal value) throws SQLException { checkClosed(); - parameters.setParameter(parameterIndex, value, Types.DECIMAL); + parameters.setParameter(parameterIndex, value, Types.NUMERIC); } @Override 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 aef5638e..65556687 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java @@ -19,6 +19,8 @@ import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.Code; import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.Date; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; @@ -44,6 +46,7 @@ static int extractColumnType(Type type) { if (type.equals(Type.date())) return Types.DATE; if (type.equals(Type.float64())) return Types.DOUBLE; if (type.equals(Type.int64())) return Types.BIGINT; + if (type.equals(Type.numeric())) return Types.NUMERIC; if (type.equals(Type.string())) return Types.NVARCHAR; if (type.equals(Type.timestamp())) return Types.TIMESTAMP; if (type.getCode() == Code.ARRAY) return Types.ARRAY; @@ -60,6 +63,8 @@ static String getSpannerTypeName(int sqlType) { || sqlType == Types.INTEGER || sqlType == Types.SMALLINT || sqlType == Types.TINYINT) return Type.int64().getCode().name(); + if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) + return Type.numeric().getCode().name(); if (sqlType == Types.NVARCHAR) return Type.string().getCode().name(); if (sqlType == Types.TIMESTAMP) return Type.timestamp().getCode().name(); if (sqlType == Types.ARRAY) return Code.ARRAY.name(); @@ -77,6 +82,7 @@ static String getClassName(int sqlType) { || sqlType == Types.INTEGER || sqlType == Types.SMALLINT || sqlType == Types.TINYINT) return Long.class.getName(); + if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) return BigDecimal.class.getName(); if (sqlType == Types.NVARCHAR) return String.class.getName(); if (sqlType == Types.TIMESTAMP) return Timestamp.class.getName(); if (sqlType == Types.ARRAY) return Object.class.getName(); @@ -96,6 +102,7 @@ static String getClassName(Type type) { if (type == Type.date()) return Date.class.getName(); if (type == Type.float64()) return Double.class.getName(); if (type == Type.int64()) return Long.class.getName(); + if (type == Type.numeric()) return BigDecimal.class.getName(); if (type == Type.string()) return String.class.getName(); if (type == Type.timestamp()) return Timestamp.class.getName(); if (type.getCode() == Code.ARRAY) { @@ -104,6 +111,7 @@ static String getClassName(Type type) { if (type.getArrayElementType() == Type.date()) return Date[].class.getName(); if (type.getArrayElementType() == Type.float64()) return Double[].class.getName(); if (type.getArrayElementType() == Type.int64()) return Long[].class.getName(); + if (type.getArrayElementType() == Type.numeric()) return BigDecimal[].class.getName(); if (type.getArrayElementType() == Type.string()) return String[].class.getName(); if (type.getArrayElementType() == Type.timestamp()) return Timestamp[].class.getName(); } @@ -122,6 +130,16 @@ static byte checkedCastToByte(long val) throws SQLException { return (byte) val; } + /** Cast value and throw {@link SQLException} if out-of-range. */ + static byte checkedCastToByte(BigDecimal val) throws SQLException { + try { + return val.byteValueExact(); + } catch (ArithmeticException e) { + throw JdbcSqlExceptionFactory.of( + String.format(OUT_OF_RANGE_MSG, "byte", val), com.google.rpc.Code.OUT_OF_RANGE); + } + } + /** Cast value and throw {@link SQLException} if out-of-range. */ static short checkedCastToShort(long val) throws SQLException { if (val > Short.MAX_VALUE || val < Short.MIN_VALUE) { @@ -131,6 +149,16 @@ static short checkedCastToShort(long val) throws SQLException { return (short) val; } + /** Cast value and throw {@link SQLException} if out-of-range. */ + static short checkedCastToShort(BigDecimal val) throws SQLException { + try { + return val.shortValueExact(); + } catch (ArithmeticException e) { + throw JdbcSqlExceptionFactory.of( + String.format(OUT_OF_RANGE_MSG, "short", val), com.google.rpc.Code.OUT_OF_RANGE); + } + } + /** Cast value and throw {@link SQLException} if out-of-range. */ static int checkedCastToInt(long val) throws SQLException { if (val > Integer.MAX_VALUE || val < Integer.MIN_VALUE) { @@ -140,6 +168,16 @@ static int checkedCastToInt(long val) throws SQLException { return (int) val; } + /** Cast value and throw {@link SQLException} if out-of-range. */ + static int checkedCastToInt(BigDecimal val) throws SQLException { + try { + return val.intValueExact(); + } catch (ArithmeticException e) { + throw JdbcSqlExceptionFactory.of( + String.format(OUT_OF_RANGE_MSG, "int", val), com.google.rpc.Code.OUT_OF_RANGE); + } + } + /** Cast value and throw {@link SQLException} if out-of-range. */ static float checkedCastToFloat(double val) throws SQLException { if (val > Float.MAX_VALUE || val < -Float.MAX_VALUE) { @@ -163,6 +201,26 @@ static long parseLong(String val) throws SQLException { } } + /** Cast value and throw {@link SQLException} if out-of-range. */ + static BigInteger checkedCastToBigInteger(BigDecimal val) throws SQLException { + try { + return val.toBigIntegerExact(); + } catch (ArithmeticException e) { + throw JdbcSqlExceptionFactory.of( + String.format(OUT_OF_RANGE_MSG, "BigInteger", val), com.google.rpc.Code.OUT_OF_RANGE); + } + } + + /** Cast value and throw {@link SQLException} if out-of-range. */ + static long checkedCastToLong(BigDecimal val) throws SQLException { + try { + return val.longValueExact(); + } catch (ArithmeticException e) { + throw JdbcSqlExceptionFactory.of( + String.format(OUT_OF_RANGE_MSG, "long", val), com.google.rpc.Code.OUT_OF_RANGE); + } + } + /** * Parses the given string value as a double. Throws {@link SQLException} if the string is not a * valid double value. diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java index 1c984c74..1dc5f0e4 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java @@ -19,6 +19,7 @@ import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.Code; +import java.math.BigDecimal; import java.sql.Date; import java.sql.Timestamp; import java.sql.Types; @@ -175,6 +176,32 @@ public Type getSpannerType() { return Type.int64(); } }, + NUMERIC { + @Override + public int getSqlType() { + return Types.NUMERIC; + } + + @Override + public Class getJavaClass() { + return BigDecimal.class; + } + + @Override + public Code getCode() { + return Code.NUMERIC; + } + + @Override + public List getArrayElements(ResultSet rs, int columnIndex) { + return rs.getBigDecimalList(columnIndex); + } + + @Override + public Type getSpannerType() { + return Type.numeric(); + } + }, STRING { @Override public int getSqlType() { diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java index 22176595..508a2c37 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java @@ -1203,6 +1203,44 @@ public ResultSet getTypeInfo() throws SQLException { .to((Long) null) .set("NUM_PREC_RADIX") .to((Long) null) + .build(), + Struct.newBuilder() + .set("TYPE_NAME") + .to("NUMERIC") + .set("DATA_TYPE") + .to(Types.NUMERIC) // 2 + .set("PRECISION") + .to(2621440L) + .set("LITERAL_PREFIX") + .to((String) null) + .set("LITERAL_SUFFIX") + .to((String) null) + .set("CREATE_PARAMS") + .to((String) null) + .set("NULLABLE") + .to(DatabaseMetaData.typeNullable) + .set("CASE_SENSITIVE") + .to(false) + .set("SEARCHABLE") + .to(DatabaseMetaData.typePredBasic) + .set("UNSIGNED_ATTRIBUTE") + .to(false) + .set("FIXED_PREC_SCALE") + .to(false) + .set("AUTO_INCREMENT") + .to(false) + .set("LOCAL_TYPE_NAME") + .to("NUMERIC") + .set("MINIMUM_SCALE") + .to(0) + .set("MAXIMUM_SCALE") + .to(0) + .set("SQL_DATA_TYPE") + .to((Long) null) + .set("SQL_DATETIME_SUB") + .to((Long) null) + .set("NUM_PREC_RADIX") + .to(10) .build()))); } diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java index f78f1f03..59b10b68 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java @@ -98,7 +98,7 @@ public int getParameterType(int param) throws SQLException { } else if (Double.class.isAssignableFrom(value.getClass())) { return Types.DOUBLE; } else if (BigDecimal.class.isAssignableFrom(value.getClass())) { - return Types.DECIMAL; + return Types.NUMERIC; } else if (Date.class.isAssignableFrom(value.getClass())) { return Types.DATE; } else if (Timestamp.class.isAssignableFrom(value.getClass())) { diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java index 5a139a9d..807666ce 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java @@ -211,11 +211,9 @@ private boolean isTypeSupported(int sqlType) { case Types.BLOB: case Types.CLOB: case Types.NCLOB: - return true; case Types.NUMERIC: case Types.DECIMAL: - // currently not supported as Cloud Spanner does not have any decimal data type. - return false; + return true; } return false; } @@ -235,11 +233,9 @@ private boolean isValidTypeAndValue(Object value, int sqlType) { case Types.FLOAT: case Types.REAL: case Types.DOUBLE: - return value instanceof Number; case Types.NUMERIC: case Types.DECIMAL: - // currently not supported as Cloud Spanner does not have any decimal data type. - return false; + return value instanceof Number; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: @@ -473,9 +469,18 @@ private Builder setParamWithKnownType(ValueBinder binder, Object value, throw JdbcSqlExceptionFactory.of(value + " is not a valid double", Code.INVALID_ARGUMENT); case Types.NUMERIC: case Types.DECIMAL: - // currently not supported as Cloud Spanner does not have any decimal data type. + if (value instanceof Number) { + if (value instanceof BigDecimal) { + return binder.to((BigDecimal) value); + } + try { + return binder.to(new BigDecimal(value.toString())); + } catch (NumberFormatException e) { + // ignore and fall through to the exception. + } + } throw JdbcSqlExceptionFactory.of( - "DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT); + value + " is not a valid BigDecimal", Code.INVALID_ARGUMENT); case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: @@ -604,8 +609,7 @@ private Builder setParamWithUnknownType(ValueBinder binder, Object valu } else if (Double.class.isAssignableFrom(value.getClass())) { return binder.to(((Double) value).doubleValue()); } else if (BigDecimal.class.isAssignableFrom(value.getClass())) { - // currently not supported - return null; + return binder.to((BigDecimal) value); } else if (Date.class.isAssignableFrom(value.getClass())) { Date dateValue = (Date) value; return binder.to(JdbcTypeConverter.toGoogleDate(dateValue)); @@ -693,8 +697,7 @@ private Builder setArrayValue(ValueBinder binder, int type, Object valu return binder.toFloat64Array((double[]) null); case Types.NUMERIC: case Types.DECIMAL: - throw JdbcSqlExceptionFactory.of( - "DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT); + return binder.toNumericArray(null); case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: @@ -755,8 +758,7 @@ private Builder setArrayValue(ValueBinder binder, int type, Object valu } else if (Double[].class.isAssignableFrom(value.getClass())) { return binder.toFloat64Array(toDoubleList((Double[]) value)); } else if (BigDecimal[].class.isAssignableFrom(value.getClass())) { - // currently not supported - return null; + return binder.toNumericArray(Arrays.asList((BigDecimal[]) value)); } else if (Date[].class.isAssignableFrom(value.getClass())) { return binder.toDateArray(JdbcTypeConverter.toGoogleDates((Date[]) value)); } else if (Timestamp[].class.isAssignableFrom(value.getClass())) { @@ -810,9 +812,7 @@ private Builder setNullValue(ValueBinder binder, Integer sqlType) throw return binder.to((com.google.cloud.Date) null); case Types.NUMERIC: case Types.DECIMAL: - // currently not supported - throw JdbcSqlExceptionFactory.of( - "DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT); + return binder.to((BigDecimal) null); case Types.DOUBLE: return binder.to((Double) null); case Types.FLOAT: 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 2e4014ba..3f10b0c1 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java @@ -138,6 +138,8 @@ public String getString(int columnIndex) throws SQLException { return isNull ? null : Double.toString(spanner.getDouble(spannerIndex)); case INT64: return isNull ? null : Long.toString(spanner.getLong(spannerIndex)); + case NUMERIC: + return isNull ? null : spanner.getBigDecimal(spannerIndex).toString(); case STRING: return isNull ? null : spanner.getString(spannerIndex); case TIMESTAMP: @@ -162,6 +164,8 @@ public boolean getBoolean(int columnIndex) throws SQLException { return isNull ? false : spanner.getDouble(spannerIndex) != 0D; case INT64: return isNull ? false : spanner.getLong(spannerIndex) != 0L; + case NUMERIC: + return isNull ? false : !spanner.getBigDecimal(spannerIndex).equals(BigDecimal.ZERO); case STRING: return isNull ? false : Boolean.valueOf(spanner.getString(spannerIndex)); case BYTES: @@ -189,6 +193,8 @@ public byte getByte(int columnIndex) throws SQLException { : checkedCastToByte(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); case INT64: return isNull ? (byte) 0 : checkedCastToByte(spanner.getLong(spannerIndex)); + case NUMERIC: + return isNull ? (byte) 0 : checkedCastToByte(spanner.getBigDecimal(spannerIndex)); case STRING: return isNull ? (byte) 0 : checkedCastToByte(parseLong(spanner.getString(spannerIndex))); case BYTES: @@ -216,6 +222,8 @@ public short getShort(int columnIndex) throws SQLException { : checkedCastToShort(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); case INT64: return isNull ? 0 : checkedCastToShort(spanner.getLong(spannerIndex)); + case NUMERIC: + return isNull ? 0 : checkedCastToShort(spanner.getBigDecimal(spannerIndex)); case STRING: return isNull ? 0 : checkedCastToShort(parseLong(spanner.getString(spannerIndex))); case BYTES: @@ -243,6 +251,8 @@ public int getInt(int columnIndex) throws SQLException { : checkedCastToInt(Double.valueOf(spanner.getDouble(spannerIndex)).longValue()); case INT64: return isNull ? 0 : checkedCastToInt(spanner.getLong(spannerIndex)); + case NUMERIC: + return isNull ? 0 : checkedCastToInt(spanner.getBigDecimal(spannerIndex)); case STRING: return isNull ? 0 : checkedCastToInt(parseLong(spanner.getString(spannerIndex))); case BYTES: @@ -268,6 +278,8 @@ public long getLong(int columnIndex) throws SQLException { return isNull ? 0L : Double.valueOf(spanner.getDouble(spannerIndex)).longValue(); case INT64: return isNull ? 0L : spanner.getLong(spannerIndex); + case NUMERIC: + return isNull ? 0L : checkedCastToLong(spanner.getBigDecimal(spannerIndex)); case STRING: return isNull ? 0L : parseLong(spanner.getString(spannerIndex)); case BYTES: @@ -293,6 +305,8 @@ public float getFloat(int columnIndex) throws SQLException { return isNull ? 0 : checkedCastToFloat(spanner.getDouble(spannerIndex)); case INT64: return isNull ? 0 : checkedCastToFloat(spanner.getLong(spannerIndex)); + case NUMERIC: + return isNull ? 0 : spanner.getBigDecimal(spannerIndex).floatValue(); case STRING: return isNull ? 0 : checkedCastToFloat(parseDouble(spanner.getString(spannerIndex))); case BYTES: @@ -318,6 +332,8 @@ public double getDouble(int columnIndex) throws SQLException { return isNull ? 0 : spanner.getDouble(spannerIndex); case INT64: return isNull ? 0 : spanner.getLong(spannerIndex); + case NUMERIC: + return isNull ? 0 : spanner.getBigDecimal(spannerIndex).doubleValue(); case STRING: return isNull ? 0 : parseDouble(spanner.getString(spannerIndex)); case BYTES: @@ -354,6 +370,7 @@ public Date getDate(int columnIndex) throws SQLException { case BOOL: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: @@ -377,6 +394,7 @@ public Time getTime(int columnIndex) throws SQLException { case DATE: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: @@ -401,6 +419,7 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException { case BOOL: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: @@ -555,6 +574,7 @@ private Object getObject(Type type, int columnIndex) throws SQLException { if (type == Type.date()) return getDate(columnIndex); if (type == Type.float64()) return getDouble(columnIndex); if (type == Type.int64()) return getLong(columnIndex); + if (type == Type.numeric()) return getBigDecimal(columnIndex); if (type == Type.string()) return getString(columnIndex); if (type == Type.timestamp()) return getTimestamp(columnIndex); if (type.getCode() == Code.ARRAY) return getArray(columnIndex); @@ -630,6 +650,9 @@ private BigDecimal getBigDecimal(int columnIndex, boolean fixedScale, int scale) case INT64: res = isNull ? null : BigDecimal.valueOf(spanner.getLong(spannerIndex)); break; + case NUMERIC: + res = isNull ? null : spanner.getBigDecimal(spannerIndex); + break; case STRING: try { res = isNull ? null : new BigDecimal(spanner.getString(spannerIndex)); @@ -724,6 +747,7 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException { case BOOL: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: @@ -752,6 +776,7 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException { case DATE: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: @@ -783,6 +808,7 @@ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException case BOOL: case FLOAT64: case INT64: + case NUMERIC: case BYTES: case STRUCT: case ARRAY: diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java index 4292c922..a598b109 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java @@ -79,7 +79,7 @@ public int isNullable(int column) throws SQLException { @Override public boolean isSigned(int column) throws SQLException { int type = getColumnType(column); - return type == Types.DOUBLE || type == Types.BIGINT; + return type == Types.DOUBLE || type == Types.BIGINT || type == Types.NUMERIC; } @Override @@ -99,6 +99,8 @@ public int getColumnDisplaySize(int column) throws SQLException { return 14; case Types.BIGINT: return 10; + case Types.NUMERIC: + return 14; case Types.NVARCHAR: int length = getPrecision(column); return length == 0 ? DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS : length; @@ -136,6 +138,8 @@ public int getPrecision(int column) throws SQLException { return 14; case Types.BIGINT: return 10; + case Types.NUMERIC: + return 14; case Types.TIMESTAMP: return 24; default: @@ -150,7 +154,7 @@ public int getPrecision(int column) throws SQLException { @Override public int getScale(int column) throws SQLException { int colType = getColumnType(column); - if (colType == Types.DOUBLE) return 15; + if (colType == Types.DOUBLE || colType == Types.NUMERIC) return 15; return 0; } 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 e922e585..c1f4b929 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java @@ -80,43 +80,58 @@ static Object convert(Object value, Type type, Class targetType) throws SQLEx if (type.getCode() == Code.BOOL) return value; if (type.getCode() == Code.INT64) return Boolean.valueOf((Long) value != 0); if (type.getCode() == Code.FLOAT64) return Boolean.valueOf((Double) value != 0d); + if (type.getCode() == Code.NUMERIC) + return Boolean.valueOf(!((BigDecimal) value).equals(BigDecimal.ZERO)); } if (targetType.equals(BigDecimal.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? BigDecimal.ONE : BigDecimal.ZERO; if (type.getCode() == Code.INT64) return BigDecimal.valueOf((Long) value); + if (type.getCode() == Code.NUMERIC) return value; } if (targetType.equals(Long.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? 1L : 0L; if (type.getCode() == Code.INT64) return value; + if (type.getCode() == Code.NUMERIC) + return AbstractJdbcWrapper.checkedCastToLong((BigDecimal) value); } if (targetType.equals(Integer.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0; if (type.getCode() == Code.INT64) return AbstractJdbcWrapper.checkedCastToInt((Long) value); + if (type.getCode() == Code.NUMERIC) + return AbstractJdbcWrapper.checkedCastToInt((BigDecimal) value); } if (targetType.equals(Short.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0; if (type.getCode() == Code.INT64) return AbstractJdbcWrapper.checkedCastToShort((Long) value); + if (type.getCode() == Code.NUMERIC) + return AbstractJdbcWrapper.checkedCastToShort((BigDecimal) value); } if (targetType.equals(Byte.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0; if (type.getCode() == Code.INT64) return AbstractJdbcWrapper.checkedCastToByte((Long) value); + if (type.getCode() == Code.NUMERIC) + return AbstractJdbcWrapper.checkedCastToByte((BigDecimal) value); } if (targetType.equals(BigInteger.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? BigInteger.ONE : BigInteger.ZERO; if (type.getCode() == Code.INT64) return BigInteger.valueOf((Long) value); + if (type.getCode() == Code.NUMERIC) + return AbstractJdbcWrapper.checkedCastToBigInteger((BigDecimal) value); } if (targetType.equals(Float.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? Float.valueOf(1f) : Float.valueOf(0f); if (type.getCode() == Code.FLOAT64) return AbstractJdbcWrapper.checkedCastToFloat((Double) value); + if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).floatValue(); } if (targetType.equals(Double.class)) { if (type.getCode() == Code.BOOL) return (Boolean) value ? Double.valueOf(1d) : Double.valueOf(0d); if (type.getCode() == Code.FLOAT64) return value; + if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).doubleValue(); } if (targetType.equals(java.sql.Date.class)) { if (type.getCode() == Code.DATE) return value; @@ -175,6 +190,10 @@ private static void checkValidTypeAndValueForConvert(Type type, Object value) (type.getCode() == Code.TIMESTAMP && value.getClass().equals(java.sql.Timestamp.class)) || type.getCode() != Code.TIMESTAMP, "input type is timestamp, but input value is not an instance of java.sql.Timestamp"); + JdbcPreconditions.checkArgument( + (type.getCode() == Code.NUMERIC && value.getClass().equals(BigDecimal.class)) + || type.getCode() != Code.NUMERIC, + "input type is numeric, but input value is not an instance of BigDecimal"); } @SuppressWarnings("deprecation") diff --git a/src/main/resources/com/google/cloud/spanner/jdbc/DatabaseMetaData_GetColumns.sql b/src/main/resources/com/google/cloud/spanner/jdbc/DatabaseMetaData_GetColumns.sql index 40601611..8b29c15b 100644 --- a/src/main/resources/com/google/cloud/spanner/jdbc/DatabaseMetaData_GetColumns.sql +++ b/src/main/resources/com/google/cloud/spanner/jdbc/DatabaseMetaData_GetColumns.sql @@ -22,6 +22,7 @@ SELECT TABLE_CATALOG AS TABLE_CAT, TABLE_SCHEMA AS TABLE_SCHEM, TABLE_NAME, COLU WHEN SPANNER_TYPE = 'DATE' THEN 91 WHEN SPANNER_TYPE = 'FLOAT64' THEN 8 WHEN SPANNER_TYPE = 'INT64' THEN -5 + WHEN SPANNER_TYPE = 'NUMERIC' THEN 2 WHEN SPANNER_TYPE LIKE 'STRING%' THEN -9 WHEN SPANNER_TYPE = 'TIMESTAMP' THEN 93 END AS DATA_TYPE, @@ -30,6 +31,7 @@ SELECT TABLE_CATALOG AS TABLE_CAT, TABLE_SCHEMA AS TABLE_SCHEM, TABLE_NAME, COLU WHEN STRPOS(SPANNER_TYPE, '(')=0 THEN CASE WHEN SPANNER_TYPE = 'INT64' OR SPANNER_TYPE = 'ARRAY' THEN 19 + WHEN SPANNER_TYPE = 'NUMERIC' OR SPANNER_TYPE = 'ARRAY' THEN 15 WHEN SPANNER_TYPE = 'FLOAT64' OR SPANNER_TYPE = 'ARRAY' THEN 15 WHEN SPANNER_TYPE = 'BOOL' OR SPANNER_TYPE = 'ARRAY' THEN NULL WHEN SPANNER_TYPE = 'DATE' OR SPANNER_TYPE = 'ARRAY' THEN 10 @@ -45,6 +47,7 @@ SELECT TABLE_CATALOG AS TABLE_CAT, TABLE_SCHEMA AS TABLE_SCHEM, TABLE_NAME, COLU END AS DECIMAL_DIGITS, CASE WHEN SPANNER_TYPE LIKE '%INT64%' THEN 10 + WHEN SPANNER_TYPE LIKE '%NUMERIC%' THEN 10 WHEN SPANNER_TYPE LIKE '%FLOAT64%' THEN 2 ELSE NULL END AS NUM_PREC_RADIX, diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java index 50a696fa..01184c2d 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; +import java.math.BigDecimal; import java.sql.Date; import java.sql.SQLException; import java.sql.Timestamp; @@ -54,6 +55,13 @@ public void testCreateArrayTypeName() throws SQLException { assertEquals(array.getBaseType(), Types.BIGINT); assertEquals(((Long[]) array.getArray(1, 1))[0], Long.valueOf(1L)); + array = + JdbcArray.createArray("NUMERIC", new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN}); + assertEquals(array.getBaseType(), Types.NUMERIC); + assertEquals(((BigDecimal[]) array.getArray(1, 1))[0], BigDecimal.ONE); + assertEquals(((BigDecimal[]) array.getArray(2, 1))[0], null); + assertEquals(((BigDecimal[]) array.getArray(3, 1))[0], BigDecimal.TEN); + array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); assertEquals(array.getBaseType(), Types.NVARCHAR); assertEquals(((String[]) array.getArray(1, 1))[0], "foo"); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java index 48d55214..b3697a07 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java @@ -461,6 +461,8 @@ public void testGetTypeInfo() throws SQLException { assertThat(rs.getString("TYPE_NAME"), is(equalTo("DATE"))); assertThat(rs.next(), is(true)); assertThat(rs.getString("TYPE_NAME"), is(equalTo("TIMESTAMP"))); + assertThat(rs.next(), is(true)); + assertThat(rs.getString("TYPE_NAME"), is(equalTo("NUMERIC"))); assertThat(rs.next(), is(false)); ResultSetMetaData rsmd = rs.getMetaData(); assertThat(rsmd.getColumnCount(), is(equalTo(18))); 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 60911a86..bdeda19c 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java @@ -328,8 +328,10 @@ public void autocommitExecuteStreamingSql() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofException(serverException)); try (java.sql.Connection connection = createConnection()) { - connection.createStatement().executeQuery(SELECT1.getSql()); - fail("missing expected exception"); + try (java.sql.ResultSet rs = connection.createStatement().executeQuery(SELECT1.getSql())) { + rs.next(); + fail("missing expected exception"); + } } catch (SQLException e) { assertThat(testExceptionMatcher.matches(e)).isTrue(); } @@ -341,8 +343,10 @@ public void transactionalExecuteStreamingSql() { SimulatedExecutionTime.ofException(serverException)); try (java.sql.Connection connection = createConnection()) { connection.setAutoCommit(false); - connection.createStatement().executeQuery(SELECT1.getSql()); - fail("missing expected exception"); + try (java.sql.ResultSet rs = connection.createStatement().executeQuery(SELECT1.getSql())) { + rs.next(); + fail("missing expected exception"); + } } catch (SQLException e) { assertThat(testExceptionMatcher.matches(e)).isTrue(); } @@ -355,8 +359,10 @@ public void readOnlyExecuteStreamingSql() { try (java.sql.Connection connection = createConnection()) { connection.setAutoCommit(false); connection.setReadOnly(true); - connection.createStatement().executeQuery(SELECT1.getSql()); - fail("missing expected exception"); + try (java.sql.ResultSet rs = connection.createStatement().executeQuery(SELECT1.getSql())) { + rs.next(); + fail("missing expected exception"); + } } catch (SQLException e) { assertThat(testExceptionMatcher.matches(e)).isTrue(); } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java index 1b201f9a..624a031e 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java @@ -120,16 +120,8 @@ public void testSetParameterWithType() throws SQLException, IOException { is(true)); verifyParameter(params, Value.string("test")); - // test unsupported types - boolean expectedException = false; - try { - params.setParameter(1, BigDecimal.ONE, Types.DECIMAL); - } catch (SQLException e) { - if (e instanceof JdbcSqlException) { - expectedException = ((JdbcSqlException) e).getCode() == Code.INVALID_ARGUMENT; - } - } - assertThat(expectedException, is(true)); + params.setParameter(1, BigDecimal.ONE, Types.DECIMAL); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); // types that should lead to int64 for (int type : new int[] {Types.TINYINT, Types.SMALLINT, Types.INTEGER, Types.BIGINT}) { @@ -281,6 +273,32 @@ public void testSetParameterWithType() throws SQLException, IOException { assertThat((BigDecimal) params.getParameter(1), is(equalTo(BigDecimal.ZERO))); verifyParameter(params, Value.bool(false)); } + + // types that should lead to numeric + for (int type : new int[] {Types.DECIMAL, Types.NUMERIC}) { + params.setParameter(1, BigDecimal.ONE, type); + assertThat((BigDecimal) params.getParameter(1), is(equalTo(BigDecimal.ONE))); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); + + params.setParameter(1, (byte) 1, type); + assertThat((Byte) params.getParameter(1), is(equalTo((byte) 1))); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); + params.setParameter(1, (short) 1, type); + assertThat((Short) params.getParameter(1), is(equalTo((short) 1))); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); + params.setParameter(1, 1, type); + assertThat((Integer) params.getParameter(1), is(equalTo(1))); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); + params.setParameter(1, 1L, type); + assertThat((Long) params.getParameter(1), is(equalTo(1L))); + verifyParameter(params, Value.numeric(BigDecimal.ONE)); + params.setParameter(1, (float) 1, type); + assertThat((Float) params.getParameter(1), is(equalTo((float) 1))); + verifyParameter(params, Value.numeric(BigDecimal.valueOf(1.0))); + params.setParameter(1, (double) 1, type); + assertThat((Double) params.getParameter(1), is(equalTo((double) 1))); + verifyParameter(params, Value.numeric(BigDecimal.valueOf(1.0))); + } } @Test @@ -353,9 +371,6 @@ public void testSetInvalidParameterWithType() throws SQLException, IOException { assertInvalidParameter(params, new Object(), type); } - assertInvalidParameter(params, BigDecimal.ONE, Types.DECIMAL); - assertInvalidParameter(params, BigDecimal.ZERO, Types.NUMERIC); - // test setting closed readers and streams. for (int type : new int[] { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java index bcf0454f..c5d93960 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java @@ -34,7 +34,6 @@ import com.google.rpc.Code; import java.io.ByteArrayInputStream; import java.io.StringReader; -import java.math.BigDecimal; import java.net.MalformedURLException; import java.net.URL; import java.sql.Date; @@ -211,15 +210,6 @@ public void testParameters() throws SQLException, MalformedURLException { private void testSetUnsupportedTypes(PreparedStatement ps) { // TODO: Rewrite these tests using functional interfaces when Java8 is available. boolean expectedException = false; - try { - ps.setBigDecimal(5, BigDecimal.valueOf(1l)); - } catch (SQLException e) { - if (e instanceof JdbcSqlException) { - expectedException = ((JdbcSqlException) e).getCode() == Code.INVALID_ARGUMENT; - } - } - assertThat(expectedException, is(true)); - expectedException = false; try { ps.setRef(38, (Ref) null); } catch (SQLException e) { 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 58ef03a1..d7c844a7 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java @@ -71,7 +71,8 @@ public void testConvertArray() throws SQLException { Type.float64(), Type.int64(), Type.string(), - Type.timestamp() + Type.timestamp(), + Type.numeric() }) { assertConvertThrows(testValue, Type.array(t), Boolean.class, Code.INVALID_ARGUMENT); assertConvertThrows(testValue, Type.array(t), Byte.class, Code.INVALID_ARGUMENT); @@ -292,6 +293,43 @@ public void testConvertFloat64() throws SQLException { } } + @Test + public void testConvertNumeric() throws SQLException { + BigDecimal[] testValues = + new BigDecimal[] { + BigDecimal.ZERO, + BigDecimal.ONE.negate(), + BigDecimal.ONE, + BigDecimal.valueOf(Double.MIN_VALUE), + BigDecimal.valueOf(Double.MAX_VALUE), + BigDecimal.valueOf(Float.MIN_VALUE), + BigDecimal.valueOf(Float.MAX_VALUE), + BigDecimal.valueOf(Float.MAX_VALUE + 1D) + }; + for (BigDecimal d : testValues) { + assertThat(convert(d, Type.numeric(), BigDecimal.class)).isEqualTo(d); + assertThat(convert(d, Type.numeric(), Double.class)).isEqualTo(d.doubleValue()); + assertThat(convert(d, Type.numeric(), Float.class)).isEqualTo(d.floatValue()); + assertThat(convert(d, Type.numeric(), String.class)).isEqualTo(String.valueOf(d)); + assertThat(convert(d, Type.numeric(), Boolean.class)) + .isEqualTo(Boolean.valueOf(!d.equals(BigDecimal.ZERO))); + if (d.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0 + || d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) < 0 + || d.scale() > 0) { + assertConvertThrows(d, Type.numeric(), Long.class, Code.OUT_OF_RANGE); + } else { + assertThat(convert(d, Type.numeric(), Long.class)).isEqualTo(d.longValue()); + } + if (d.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE)) > 0 + || d.compareTo(BigDecimal.valueOf(Integer.MIN_VALUE)) < 0 + || d.scale() > 0) { + assertConvertThrows(d, Type.numeric(), Integer.class, Code.OUT_OF_RANGE); + } else { + assertThat(convert(d, Type.numeric(), Integer.class)).isEqualTo(d.intValue()); + } + } + } + private void assertConvertThrows(Object t, Type type, Class destinationType, Code code) throws SQLException { try { 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 4cd5e9b9..be5fed23 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java @@ -55,7 +55,7 @@ public boolean matches(Object item) { return exception.getErrorCode() == errorCode.getNumber(); } return exception.getErrorCode() == errorCode.getNumber() - && exception.getMessage().endsWith(": " + message); + && exception.getMessage().contains(": " + message); } return false; } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java index 9cf57b3f..711363c2 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java @@ -104,6 +104,7 @@ private Column( new Column("ColDate", Types.DATE, "DATE", 10, null, null, false, null), new Column("ColTimestamp", Types.TIMESTAMP, "TIMESTAMP", 35, null, null, false, null), new Column("ColCommitTS", Types.TIMESTAMP, "TIMESTAMP", 35, null, null, false, null), + new Column("ColNumeric", Types.NUMERIC, "NUMERIC", 15, null, 10, false, null), new Column("ColInt64Array", Types.ARRAY, "ARRAY", 19, null, 10, true, null), new Column("ColFloat64Array", Types.ARRAY, "ARRAY", 15, 16, 2, true, null), new Column("ColBoolArray", Types.ARRAY, "ARRAY", null, null, null, true, null), @@ -131,7 +132,8 @@ private Column( null), new Column("ColDateArray", Types.ARRAY, "ARRAY", 10, null, null, true, null), new Column( - "ColTimestampArray", Types.ARRAY, "ARRAY", 35, null, null, true, null)); + "ColTimestampArray", Types.ARRAY, "ARRAY", 35, null, null, true, null), + new Column("ColNumericArray", Types.ARRAY, "ARRAY", 15, null, 10, true, null)); @Test public void testGetColumns() throws SQLException { diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java index 60ddb3c2..799b494d 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java @@ -31,6 +31,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.StringReader; +import java.math.BigDecimal; import java.sql.BatchUpdateException; import java.sql.Connection; import java.sql.Date; @@ -749,6 +750,94 @@ public void test07_StatementBatchUpdateWithException() throws SQLException { } } + @Test + public void test08_InsertAllColumnTypes() throws SQLException { + try (Connection con = createConnection()) { + try (PreparedStatement ps = + con.prepareStatement( + "INSERT INTO TableWithAllColumnTypes (" + + "ColInt64, ColFloat64, ColBool, ColString, ColStringMax, ColBytes, ColBytesMax, ColDate, ColTimestamp, ColCommitTS, ColNumeric, " + + "ColInt64Array, ColFloat64Array, ColBoolArray, ColStringArray, ColStringMaxArray, ColBytesArray, ColBytesMaxArray, ColDateArray, ColTimestampArray, ColNumericArray" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, PENDING_COMMIT_TIMESTAMP(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) { + ps.setLong(1, 1L); + ps.setDouble(2, 2D); + ps.setBoolean(3, true); + ps.setString(4, "test"); + ps.setString(5, "testtest"); + ps.setBytes(6, "test".getBytes()); + ps.setBytes(7, "testtest".getBytes()); + ps.setDate(8, new Date(System.currentTimeMillis())); + ps.setTimestamp(9, new Timestamp(System.currentTimeMillis())); + ps.setBigDecimal(10, BigDecimal.TEN); + ps.setArray(11, con.createArrayOf("INT64", new Long[] {1L, 2L, 3L})); + ps.setArray(12, con.createArrayOf("FLOAT64", new Double[] {1.1D, 2.2D, 3.3D})); + ps.setArray( + 13, con.createArrayOf("BOOL", new Boolean[] {Boolean.TRUE, null, Boolean.FALSE})); + ps.setArray(14, con.createArrayOf("STRING", new String[] {"1", "2", "3"})); + ps.setArray(15, con.createArrayOf("STRING", new String[] {"3", "2", "1"})); + ps.setArray( + 16, + con.createArrayOf( + "BYTES", new byte[][] {"1".getBytes(), "2".getBytes(), "3".getBytes()})); + ps.setArray( + 17, + con.createArrayOf( + "BYTES", new byte[][] {"333".getBytes(), "222".getBytes(), "111".getBytes()})); + ps.setArray( + 18, + con.createArrayOf( + "DATE", new Date[] {new Date(System.currentTimeMillis()), null, new Date(0)})); + ps.setArray( + 19, + con.createArrayOf( + "TIMESTAMP", + new Timestamp[] { + new Timestamp(System.currentTimeMillis()), null, new Timestamp(0) + })); + ps.setArray( + 20, + con.createArrayOf("NUMERIC", new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN})); + assertThat(ps.executeUpdate(), is(equalTo(1))); + } + try (ResultSet rs = + con.createStatement().executeQuery("SELECT * FROM TableWithAllColumnTypes")) { + assertThat(rs.next(), is(true)); + assertThat(rs.getLong(1), is(equalTo(1L))); + assertThat(rs.getDouble(2), is(equalTo(2D))); + assertThat(rs.getBoolean(3), is(true)); + assertThat(rs.getString(4), is(equalTo("test"))); + assertThat(rs.getString(5), is(equalTo("testtest"))); + assertThat(rs.getBytes(6), is(equalTo("test".getBytes()))); + assertThat(rs.getBytes(7), is(equalTo("testtest".getBytes()))); + assertThat(rs.getDate(8), is(notNullValue())); + assertThat(rs.getTimestamp(9), is(notNullValue())); + assertThat(rs.getTime(10), is(notNullValue())); // Commit timestamp + assertThat(rs.getBigDecimal(11), is(equalTo(BigDecimal.TEN))); + assertThat((Long[]) rs.getArray(12).getArray(), is(equalTo(new Long[] {1L, 2L, 3L}))); + assertThat( + (Double[]) rs.getArray(13).getArray(), is(equalTo(new Double[] {1.1D, 2.2D, 3.3D}))); + assertThat( + (Boolean[]) rs.getArray(14).getArray(), is(equalTo(new Boolean[] {true, null, false}))); + assertThat( + (String[]) rs.getArray(15).getArray(), is(equalTo(new String[] {"1", "2", "3"}))); + assertThat( + (String[]) rs.getArray(16).getArray(), is(equalTo(new String[] {"3", "2", "1"}))); + assertThat( + (byte[][]) rs.getArray(17).getArray(), + is(equalTo(new byte[][] {"1".getBytes(), "2".getBytes(), "3".getBytes()}))); + assertThat( + (byte[][]) rs.getArray(18).getArray(), + is(equalTo(new byte[][] {"333".getBytes(), "222".getBytes(), "111".getBytes()}))); + assertThat(((Date[]) rs.getArray(19).getArray()).length, is(equalTo(3))); + assertThat(((Timestamp[]) rs.getArray(20).getArray()).length, is(equalTo(3))); + assertThat( + (BigDecimal[]) rs.getArray(21).getArray(), + is(equalTo(new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN}))); + assertThat(rs.next(), is(false)); + } + } + } + private void assertDefaultParameterMetaData(ParameterMetaData pmd, int expectedParamCount) throws SQLException { assertThat(pmd.getParameterCount(), is(equalTo(expectedParamCount))); diff --git a/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables.sql b/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables.sql index 5bbb90f6..4d05cdfb 100644 --- a/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables.sql +++ b/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables.sql @@ -73,6 +73,7 @@ CREATE TABLE TableWithAllColumnTypes ( ColDate DATE NOT NULL, ColTimestamp TIMESTAMP NOT NULL, ColCommitTS TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true), + ColNumeric NUMERIC NOT NULL, ColInt64Array ARRAY, ColFloat64Array ARRAY, @@ -82,7 +83,8 @@ CREATE TABLE TableWithAllColumnTypes ( ColBytesArray ARRAY, ColBytesMaxArray ARRAY, ColDateArray ARRAY, - ColTimestampArray ARRAY + ColTimestampArray ARRAY, + ColNumericArray ARRAY ) PRIMARY KEY (ColInt64) ;