Skip to content

Commit

Permalink
feat: add support for NUMERIC type (#185)
Browse files Browse the repository at this point in the history
* feat: add support for NUMERIC type

* deps: no snapshot

* fix: add accidentally removed test script

* fix: remove spanner version
  • Loading branch information
olavloite committed Sep 4, 2020
1 parent b5a7cb8 commit 4579249
Show file tree
Hide file tree
Showing 20 changed files with 381 additions and 54 deletions.
Expand Up @@ -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
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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) {
Expand All @@ -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();
}
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
Expand Up @@ -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;
Expand Down Expand Up @@ -175,6 +176,32 @@ public Type getSpannerType() {
return Type.int64();
}
},
NUMERIC {
@Override
public int getSqlType() {
return Types.NUMERIC;
}

@Override
public Class<BigDecimal> getJavaClass() {
return BigDecimal.class;
}

@Override
public Code getCode() {
return Code.NUMERIC;
}

@Override
public List<BigDecimal> getArrayElements(ResultSet rs, int columnIndex) {
return rs.getBigDecimalList(columnIndex);
}

@Override
public Type getSpannerType() {
return Type.numeric();
}
},
STRING {
@Override
public int getSqlType() {
Expand Down
Expand Up @@ -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())));
}

Expand Down
Expand Up @@ -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())) {
Expand Down
34 changes: 17 additions & 17 deletions src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
Expand Up @@ -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;
}
Expand All @@ -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:
Expand Down Expand Up @@ -473,9 +469,18 @@ private Builder setParamWithKnownType(ValueBinder<Builder> 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:
Expand Down Expand Up @@ -604,8 +609,7 @@ private Builder setParamWithUnknownType(ValueBinder<Builder> 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));
Expand Down Expand Up @@ -693,8 +697,7 @@ private Builder setArrayValue(ValueBinder<Builder> 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:
Expand Down Expand Up @@ -755,8 +758,7 @@ private Builder setArrayValue(ValueBinder<Builder> 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())) {
Expand Down Expand Up @@ -810,9 +812,7 @@ private Builder setNullValue(ValueBinder<Builder> 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:
Expand Down

0 comments on commit 4579249

Please sign in to comment.