Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for NUMERIC type #185

Merged
merged 4 commits into from Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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