Skip to content

Commit 4579249

Browse files
authored
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
1 parent b5a7cb8 commit 4579249

20 files changed

+381
-54
lines changed

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public void setDouble(int parameterIndex, double value) throws SQLException {
128128
@Override
129129
public void setBigDecimal(int parameterIndex, BigDecimal value) throws SQLException {
130130
checkClosed();
131-
parameters.setParameter(parameterIndex, value, Types.DECIMAL);
131+
parameters.setParameter(parameterIndex, value, Types.NUMERIC);
132132
}
133133

134134
@Override

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.google.cloud.spanner.Type;
2020
import com.google.cloud.spanner.Type.Code;
2121
import com.google.common.base.Preconditions;
22+
import java.math.BigDecimal;
23+
import java.math.BigInteger;
2224
import java.sql.Date;
2325
import java.sql.SQLException;
2426
import java.sql.SQLFeatureNotSupportedException;
@@ -44,6 +46,7 @@ static int extractColumnType(Type type) {
4446
if (type.equals(Type.date())) return Types.DATE;
4547
if (type.equals(Type.float64())) return Types.DOUBLE;
4648
if (type.equals(Type.int64())) return Types.BIGINT;
49+
if (type.equals(Type.numeric())) return Types.NUMERIC;
4750
if (type.equals(Type.string())) return Types.NVARCHAR;
4851
if (type.equals(Type.timestamp())) return Types.TIMESTAMP;
4952
if (type.getCode() == Code.ARRAY) return Types.ARRAY;
@@ -60,6 +63,8 @@ static String getSpannerTypeName(int sqlType) {
6063
|| sqlType == Types.INTEGER
6164
|| sqlType == Types.SMALLINT
6265
|| sqlType == Types.TINYINT) return Type.int64().getCode().name();
66+
if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL)
67+
return Type.numeric().getCode().name();
6368
if (sqlType == Types.NVARCHAR) return Type.string().getCode().name();
6469
if (sqlType == Types.TIMESTAMP) return Type.timestamp().getCode().name();
6570
if (sqlType == Types.ARRAY) return Code.ARRAY.name();
@@ -77,6 +82,7 @@ static String getClassName(int sqlType) {
7782
|| sqlType == Types.INTEGER
7883
|| sqlType == Types.SMALLINT
7984
|| sqlType == Types.TINYINT) return Long.class.getName();
85+
if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) return BigDecimal.class.getName();
8086
if (sqlType == Types.NVARCHAR) return String.class.getName();
8187
if (sqlType == Types.TIMESTAMP) return Timestamp.class.getName();
8288
if (sqlType == Types.ARRAY) return Object.class.getName();
@@ -96,6 +102,7 @@ static String getClassName(Type type) {
96102
if (type == Type.date()) return Date.class.getName();
97103
if (type == Type.float64()) return Double.class.getName();
98104
if (type == Type.int64()) return Long.class.getName();
105+
if (type == Type.numeric()) return BigDecimal.class.getName();
99106
if (type == Type.string()) return String.class.getName();
100107
if (type == Type.timestamp()) return Timestamp.class.getName();
101108
if (type.getCode() == Code.ARRAY) {
@@ -104,6 +111,7 @@ static String getClassName(Type type) {
104111
if (type.getArrayElementType() == Type.date()) return Date[].class.getName();
105112
if (type.getArrayElementType() == Type.float64()) return Double[].class.getName();
106113
if (type.getArrayElementType() == Type.int64()) return Long[].class.getName();
114+
if (type.getArrayElementType() == Type.numeric()) return BigDecimal[].class.getName();
107115
if (type.getArrayElementType() == Type.string()) return String[].class.getName();
108116
if (type.getArrayElementType() == Type.timestamp()) return Timestamp[].class.getName();
109117
}
@@ -122,6 +130,16 @@ static byte checkedCastToByte(long val) throws SQLException {
122130
return (byte) val;
123131
}
124132

133+
/** Cast value and throw {@link SQLException} if out-of-range. */
134+
static byte checkedCastToByte(BigDecimal val) throws SQLException {
135+
try {
136+
return val.byteValueExact();
137+
} catch (ArithmeticException e) {
138+
throw JdbcSqlExceptionFactory.of(
139+
String.format(OUT_OF_RANGE_MSG, "byte", val), com.google.rpc.Code.OUT_OF_RANGE);
140+
}
141+
}
142+
125143
/** Cast value and throw {@link SQLException} if out-of-range. */
126144
static short checkedCastToShort(long val) throws SQLException {
127145
if (val > Short.MAX_VALUE || val < Short.MIN_VALUE) {
@@ -131,6 +149,16 @@ static short checkedCastToShort(long val) throws SQLException {
131149
return (short) val;
132150
}
133151

152+
/** Cast value and throw {@link SQLException} if out-of-range. */
153+
static short checkedCastToShort(BigDecimal val) throws SQLException {
154+
try {
155+
return val.shortValueExact();
156+
} catch (ArithmeticException e) {
157+
throw JdbcSqlExceptionFactory.of(
158+
String.format(OUT_OF_RANGE_MSG, "short", val), com.google.rpc.Code.OUT_OF_RANGE);
159+
}
160+
}
161+
134162
/** Cast value and throw {@link SQLException} if out-of-range. */
135163
static int checkedCastToInt(long val) throws SQLException {
136164
if (val > Integer.MAX_VALUE || val < Integer.MIN_VALUE) {
@@ -140,6 +168,16 @@ static int checkedCastToInt(long val) throws SQLException {
140168
return (int) val;
141169
}
142170

171+
/** Cast value and throw {@link SQLException} if out-of-range. */
172+
static int checkedCastToInt(BigDecimal val) throws SQLException {
173+
try {
174+
return val.intValueExact();
175+
} catch (ArithmeticException e) {
176+
throw JdbcSqlExceptionFactory.of(
177+
String.format(OUT_OF_RANGE_MSG, "int", val), com.google.rpc.Code.OUT_OF_RANGE);
178+
}
179+
}
180+
143181
/** Cast value and throw {@link SQLException} if out-of-range. */
144182
static float checkedCastToFloat(double val) throws SQLException {
145183
if (val > Float.MAX_VALUE || val < -Float.MAX_VALUE) {
@@ -163,6 +201,26 @@ static long parseLong(String val) throws SQLException {
163201
}
164202
}
165203

204+
/** Cast value and throw {@link SQLException} if out-of-range. */
205+
static BigInteger checkedCastToBigInteger(BigDecimal val) throws SQLException {
206+
try {
207+
return val.toBigIntegerExact();
208+
} catch (ArithmeticException e) {
209+
throw JdbcSqlExceptionFactory.of(
210+
String.format(OUT_OF_RANGE_MSG, "BigInteger", val), com.google.rpc.Code.OUT_OF_RANGE);
211+
}
212+
}
213+
214+
/** Cast value and throw {@link SQLException} if out-of-range. */
215+
static long checkedCastToLong(BigDecimal val) throws SQLException {
216+
try {
217+
return val.longValueExact();
218+
} catch (ArithmeticException e) {
219+
throw JdbcSqlExceptionFactory.of(
220+
String.format(OUT_OF_RANGE_MSG, "long", val), com.google.rpc.Code.OUT_OF_RANGE);
221+
}
222+
}
223+
166224
/**
167225
* Parses the given string value as a double. Throws {@link SQLException} if the string is not a
168226
* valid double value.

src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.cloud.spanner.ResultSet;
2020
import com.google.cloud.spanner.Type;
2121
import com.google.cloud.spanner.Type.Code;
22+
import java.math.BigDecimal;
2223
import java.sql.Date;
2324
import java.sql.Timestamp;
2425
import java.sql.Types;
@@ -175,6 +176,32 @@ public Type getSpannerType() {
175176
return Type.int64();
176177
}
177178
},
179+
NUMERIC {
180+
@Override
181+
public int getSqlType() {
182+
return Types.NUMERIC;
183+
}
184+
185+
@Override
186+
public Class<BigDecimal> getJavaClass() {
187+
return BigDecimal.class;
188+
}
189+
190+
@Override
191+
public Code getCode() {
192+
return Code.NUMERIC;
193+
}
194+
195+
@Override
196+
public List<BigDecimal> getArrayElements(ResultSet rs, int columnIndex) {
197+
return rs.getBigDecimalList(columnIndex);
198+
}
199+
200+
@Override
201+
public Type getSpannerType() {
202+
return Type.numeric();
203+
}
204+
},
178205
STRING {
179206
@Override
180207
public int getSqlType() {

src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,44 @@ public ResultSet getTypeInfo() throws SQLException {
12031203
.to((Long) null)
12041204
.set("NUM_PREC_RADIX")
12051205
.to((Long) null)
1206+
.build(),
1207+
Struct.newBuilder()
1208+
.set("TYPE_NAME")
1209+
.to("NUMERIC")
1210+
.set("DATA_TYPE")
1211+
.to(Types.NUMERIC) // 2
1212+
.set("PRECISION")
1213+
.to(2621440L)
1214+
.set("LITERAL_PREFIX")
1215+
.to((String) null)
1216+
.set("LITERAL_SUFFIX")
1217+
.to((String) null)
1218+
.set("CREATE_PARAMS")
1219+
.to((String) null)
1220+
.set("NULLABLE")
1221+
.to(DatabaseMetaData.typeNullable)
1222+
.set("CASE_SENSITIVE")
1223+
.to(false)
1224+
.set("SEARCHABLE")
1225+
.to(DatabaseMetaData.typePredBasic)
1226+
.set("UNSIGNED_ATTRIBUTE")
1227+
.to(false)
1228+
.set("FIXED_PREC_SCALE")
1229+
.to(false)
1230+
.set("AUTO_INCREMENT")
1231+
.to(false)
1232+
.set("LOCAL_TYPE_NAME")
1233+
.to("NUMERIC")
1234+
.set("MINIMUM_SCALE")
1235+
.to(0)
1236+
.set("MAXIMUM_SCALE")
1237+
.to(0)
1238+
.set("SQL_DATA_TYPE")
1239+
.to((Long) null)
1240+
.set("SQL_DATETIME_SUB")
1241+
.to((Long) null)
1242+
.set("NUM_PREC_RADIX")
1243+
.to(10)
12061244
.build())));
12071245
}
12081246

src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public int getParameterType(int param) throws SQLException {
9898
} else if (Double.class.isAssignableFrom(value.getClass())) {
9999
return Types.DOUBLE;
100100
} else if (BigDecimal.class.isAssignableFrom(value.getClass())) {
101-
return Types.DECIMAL;
101+
return Types.NUMERIC;
102102
} else if (Date.class.isAssignableFrom(value.getClass())) {
103103
return Types.DATE;
104104
} else if (Timestamp.class.isAssignableFrom(value.getClass())) {

src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,9 @@ private boolean isTypeSupported(int sqlType) {
211211
case Types.BLOB:
212212
case Types.CLOB:
213213
case Types.NCLOB:
214-
return true;
215214
case Types.NUMERIC:
216215
case Types.DECIMAL:
217-
// currently not supported as Cloud Spanner does not have any decimal data type.
218-
return false;
216+
return true;
219217
}
220218
return false;
221219
}
@@ -235,11 +233,9 @@ private boolean isValidTypeAndValue(Object value, int sqlType) {
235233
case Types.FLOAT:
236234
case Types.REAL:
237235
case Types.DOUBLE:
238-
return value instanceof Number;
239236
case Types.NUMERIC:
240237
case Types.DECIMAL:
241-
// currently not supported as Cloud Spanner does not have any decimal data type.
242-
return false;
238+
return value instanceof Number;
243239
case Types.CHAR:
244240
case Types.VARCHAR:
245241
case Types.LONGVARCHAR:
@@ -473,9 +469,18 @@ private Builder setParamWithKnownType(ValueBinder<Builder> binder, Object value,
473469
throw JdbcSqlExceptionFactory.of(value + " is not a valid double", Code.INVALID_ARGUMENT);
474470
case Types.NUMERIC:
475471
case Types.DECIMAL:
476-
// currently not supported as Cloud Spanner does not have any decimal data type.
472+
if (value instanceof Number) {
473+
if (value instanceof BigDecimal) {
474+
return binder.to((BigDecimal) value);
475+
}
476+
try {
477+
return binder.to(new BigDecimal(value.toString()));
478+
} catch (NumberFormatException e) {
479+
// ignore and fall through to the exception.
480+
}
481+
}
477482
throw JdbcSqlExceptionFactory.of(
478-
"DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT);
483+
value + " is not a valid BigDecimal", Code.INVALID_ARGUMENT);
479484
case Types.CHAR:
480485
case Types.VARCHAR:
481486
case Types.LONGVARCHAR:
@@ -604,8 +609,7 @@ private Builder setParamWithUnknownType(ValueBinder<Builder> binder, Object valu
604609
} else if (Double.class.isAssignableFrom(value.getClass())) {
605610
return binder.to(((Double) value).doubleValue());
606611
} else if (BigDecimal.class.isAssignableFrom(value.getClass())) {
607-
// currently not supported
608-
return null;
612+
return binder.to((BigDecimal) value);
609613
} else if (Date.class.isAssignableFrom(value.getClass())) {
610614
Date dateValue = (Date) value;
611615
return binder.to(JdbcTypeConverter.toGoogleDate(dateValue));
@@ -693,8 +697,7 @@ private Builder setArrayValue(ValueBinder<Builder> binder, int type, Object valu
693697
return binder.toFloat64Array((double[]) null);
694698
case Types.NUMERIC:
695699
case Types.DECIMAL:
696-
throw JdbcSqlExceptionFactory.of(
697-
"DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT);
700+
return binder.toNumericArray(null);
698701
case Types.CHAR:
699702
case Types.VARCHAR:
700703
case Types.LONGVARCHAR:
@@ -755,8 +758,7 @@ private Builder setArrayValue(ValueBinder<Builder> binder, int type, Object valu
755758
} else if (Double[].class.isAssignableFrom(value.getClass())) {
756759
return binder.toFloat64Array(toDoubleList((Double[]) value));
757760
} else if (BigDecimal[].class.isAssignableFrom(value.getClass())) {
758-
// currently not supported
759-
return null;
761+
return binder.toNumericArray(Arrays.asList((BigDecimal[]) value));
760762
} else if (Date[].class.isAssignableFrom(value.getClass())) {
761763
return binder.toDateArray(JdbcTypeConverter.toGoogleDates((Date[]) value));
762764
} else if (Timestamp[].class.isAssignableFrom(value.getClass())) {
@@ -810,9 +812,7 @@ private Builder setNullValue(ValueBinder<Builder> binder, Integer sqlType) throw
810812
return binder.to((com.google.cloud.Date) null);
811813
case Types.NUMERIC:
812814
case Types.DECIMAL:
813-
// currently not supported
814-
throw JdbcSqlExceptionFactory.of(
815-
"DECIMAL/NUMERIC values are not supported", Code.INVALID_ARGUMENT);
815+
return binder.to((BigDecimal) null);
816816
case Types.DOUBLE:
817817
return binder.to((Double) null);
818818
case Types.FLOAT:

0 commit comments

Comments
 (0)