18 files changed

+578
-126
lines changed
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static int extractColumnType(Type type) {
4848
if (type.equals(Type.int64())) return Types.BIGINT;
4949
if (type.equals(Type.numeric())) return Types.NUMERIC;
5050
if (type.equals(Type.string())) return Types.NVARCHAR;
51+
if (type.equals(Type.json())) return Types.NVARCHAR;
5152
if (type.equals(Type.timestamp())) return Types.TIMESTAMP;
5253
if (type.getCode() == Code.ARRAY) return Types.ARRAY;
5354
return Types.OTHER;
@@ -106,6 +107,7 @@ static String getClassName(Type type) {
106107
if (type == Type.int64()) return Long.class.getName();
107108
if (type == Type.numeric()) return BigDecimal.class.getName();
108109
if (type == Type.string()) return String.class.getName();
110+
if (type == Type.json()) return String.class.getName();
109111
if (type == Type.timestamp()) return Timestamp.class.getName();
110112
if (type.getCode() == Code.ARRAY) {
111113
if (type.getArrayElementType() == Type.bool()) return Boolean[].class.getName();
@@ -115,6 +117,7 @@ static String getClassName(Type type) {
115117
if (type.getArrayElementType() == Type.int64()) return Long[].class.getName();
116118
if (type.getArrayElementType() == Type.numeric()) return BigDecimal[].class.getName();
117119
if (type.getArrayElementType() == Type.string()) return String[].class.getName();
120+
if (type.getArrayElementType() == Type.json()) return String[].class.getName();
118121
if (type.getArrayElementType() == Type.timestamp()) return Timestamp[].class.getName();
119122
}
120123
return null;
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.cloud.spanner.Struct;
2222
import com.google.cloud.spanner.Type;
2323
import com.google.cloud.spanner.Type.StructField;
24+
import com.google.cloud.spanner.Value;
2425
import com.google.cloud.spanner.ValueBinder;
2526
import com.google.common.collect.ImmutableList;
2627
import com.google.rpc.Code;
@@ -201,6 +202,9 @@ public ResultSet getResultSet(long startIndex, int count) throws SQLException {
201202
case STRING:
202203
builder = binder.to((String) value);
203204
break;
205+
case JSON:
206+
builder = binder.to(Value.json((String) value));
207+
break;
204208
case TIMESTAMP:
205209
builder = binder.to(JdbcTypeConverter.toGoogleTimestamp((Timestamp) value));
206210
break;
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,32 @@ public Type getSpannerType() {
229229
return Type.string();
230230
}
231231
},
232+
JSON {
233+
@Override
234+
public int getSqlType() {
235+
return JsonType.VENDOR_TYPE_NUMBER;
236+
}
237+
238+
@Override
239+
public Class<String> getJavaClass() {
240+
return String.class;
241+
}
242+
243+
@Override
244+
public Code getCode() {
245+
return Code.JSON;
246+
}
247+
248+
@Override
249+
public List<String> getArrayElements(ResultSet rs, int columnIndex) {
250+
return rs.getJsonList(columnIndex);
251+
}
252+
253+
@Override
254+
public Type getSpannerType() {
255+
return Type.json();
256+
}
257+
},
232258
TIMESTAMP {
233259
@Override
234260
public int getSqlType() {
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.cloud.ByteArray;
2020
import com.google.cloud.spanner.Statement;
2121
import com.google.cloud.spanner.Statement.Builder;
22+
import com.google.cloud.spanner.Type;
2223
import com.google.cloud.spanner.Value;
2324
import com.google.cloud.spanner.ValueBinder;
2425
import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl;
@@ -264,6 +265,7 @@ private boolean isTypeSupported(int sqlType) {
264265
case Types.NCLOB:
265266
case Types.NUMERIC:
266267
case Types.DECIMAL:
268+
case JsonType.VENDOR_TYPE_NUMBER:
267269
return true;
268270
}
269271
return false;
@@ -315,6 +317,11 @@ private boolean isValidTypeAndValue(Object value, int sqlType) {
315317
return value instanceof Clob || value instanceof Reader;
316318
case Types.NCLOB:
317319
return value instanceof NClob || value instanceof Reader;
320+
case JsonType.VENDOR_TYPE_NUMBER:
321+
return value instanceof String
322+
|| value instanceof InputStream
323+
|| value instanceof Reader
324+
|| (value instanceof Value && ((Value) value).getType().getCode() == Type.Code.JSON);
318325
}
319326
return false;
320327
}
@@ -544,30 +551,34 @@ private Builder setParamWithKnownType(ValueBinder<Builder> binder, Object value,
544551
case Types.NCHAR:
545552
case Types.NVARCHAR:
546553
case Types.LONGNVARCHAR:
554+
String stringValue;
547555
if (value instanceof String) {
548-
return binder.to((String) value);
556+
stringValue = (String) value;
549557
} else if (value instanceof InputStream) {
550-
InputStreamReader reader =
551-
new InputStreamReader((InputStream) value, StandardCharsets.US_ASCII);
552-
try {
553-
return binder.to(CharStreams.toString(reader));
554-
} catch (IOException e) {
555-
throw JdbcSqlExceptionFactory.of(
556-
"could not set string from input stream", Code.INVALID_ARGUMENT, e);
557-
}
558+
stringValue = getStringFromInputStream((InputStream) value);
558559
} else if (value instanceof Reader) {
559-
try {
560-
return binder.to(CharStreams.toString((Reader) value));
561-
} catch (IOException e) {
562-
throw JdbcSqlExceptionFactory.of(
563-
"could not set string from reader", Code.INVALID_ARGUMENT, e);
564-
}
560+
stringValue = getStringFromReader((Reader) value);
565561
} else if (value instanceof URL) {
566-
return binder.to(((URL) value).toString());
562+
stringValue = ((URL) value).toString();
567563
} else if (value instanceof UUID) {
568-
return binder.to(((UUID) value).toString());
564+
stringValue = ((UUID) value).toString();
565+
} else {
566+
throw JdbcSqlExceptionFactory.of(value + " is not a valid string", Code.INVALID_ARGUMENT);
567+
}
568+
return binder.to(stringValue);
569+
case JsonType.VENDOR_TYPE_NUMBER:
570+
String jsonValue;
571+
if (value instanceof String) {
572+
jsonValue = (String) value;
573+
} else if (value instanceof InputStream) {
574+
jsonValue = getStringFromInputStream((InputStream) value);
575+
} else if (value instanceof Reader) {
576+
jsonValue = getStringFromReader((Reader) value);
577+
} else {
578+
throw JdbcSqlExceptionFactory.of(
579+
value + " is not a valid JSON value", Code.INVALID_ARGUMENT);
569580
}
570-
throw JdbcSqlExceptionFactory.of(value + " is not a valid string", Code.INVALID_ARGUMENT);
581+
return binder.to(Value.json(jsonValue));
571582
case Types.DATE:
572583
if (value instanceof Date) {
573584
return binder.to(JdbcTypeConverter.toGoogleDate((Date) value));
@@ -652,6 +663,25 @@ private Builder setParamWithKnownType(ValueBinder<Builder> binder, Object value,
652663
return null;
653664
}
654665

666+
private String getStringFromInputStream(InputStream inputStream) throws SQLException {
667+
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII);
668+
try {
669+
return CharStreams.toString(reader);
670+
} catch (IOException e) {
671+
throw JdbcSqlExceptionFactory.of(
672+
"could not set string from input stream", Code.INVALID_ARGUMENT, e);
673+
}
674+
}
675+
676+
private String getStringFromReader(Reader reader) throws SQLException {
677+
try {
678+
return CharStreams.toString(reader);
679+
} catch (IOException e) {
680+
throw JdbcSqlExceptionFactory.of(
681+
"could not set string from reader", Code.INVALID_ARGUMENT, e);
682+
}
683+
}
684+
655685
/** Set the parameter value based purely on the type of the value. */
656686
private Builder setParamWithUnknownType(ValueBinder<Builder> binder, Object value)
657687
throws SQLException {
@@ -769,14 +799,16 @@ private Builder setArrayValue(ValueBinder<Builder> binder, int type, Object valu
769799
case Types.LONGNVARCHAR:
770800
case Types.CLOB:
771801
case Types.NCLOB:
772-
return binder.toStringArray((Iterable<String>) null);
802+
return binder.toStringArray(null);
803+
case JsonType.VENDOR_TYPE_NUMBER:
804+
return binder.toJsonArray(null);
773805
case Types.DATE:
774-
return binder.toDateArray((Iterable<com.google.cloud.Date>) null);
806+
return binder.toDateArray(null);
775807
case Types.TIME:
776808
case Types.TIME_WITH_TIMEZONE:
777809
case Types.TIMESTAMP:
778810
case Types.TIMESTAMP_WITH_TIMEZONE:
779-
return binder.toTimestampArray((Iterable<com.google.cloud.Timestamp>) null);
811+
return binder.toTimestampArray(null);
780812
case Types.BINARY:
781813
case Types.VARBINARY:
782814
case Types.LONGVARBINARY:
@@ -829,7 +861,11 @@ private Builder setArrayValue(ValueBinder<Builder> binder, int type, Object valu
829861
} else if (Timestamp[].class.isAssignableFrom(value.getClass())) {
830862
return binder.toTimestampArray(JdbcTypeConverter.toGoogleTimestamps((Timestamp[]) value));
831863
} else if (String[].class.isAssignableFrom(value.getClass())) {
832-
return binder.toStringArray(Arrays.asList((String[]) value));
864+
if (type == JsonType.VENDOR_TYPE_NUMBER) {
865+
return binder.toJsonArray(Arrays.asList((String[]) value));
866+
} else {
867+
return binder.toStringArray(Arrays.asList((String[]) value));
868+
}
833869
} else if (byte[][].class.isAssignableFrom(value.getClass())) {
834870
return binder.toBytesArray(JdbcTypeConverter.toGoogleBytes((byte[][]) value));
835871
}
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ public String getString(int columnIndex) throws SQLException {
142142
return isNull ? null : spanner.getBigDecimal(spannerIndex).toString();
143143
case STRING:
144144
return isNull ? null : spanner.getString(spannerIndex);
145+
case JSON:
146+
return isNull ? null : spanner.getJson(spannerIndex);
145147
case TIMESTAMP:
146148
return isNull ? null : spanner.getTimestamp(spannerIndex).toString();
147149
case STRUCT:
@@ -169,6 +171,7 @@ public boolean getBoolean(int columnIndex) throws SQLException {
169171
case STRING:
170172
return isNull ? false : Boolean.valueOf(spanner.getString(spannerIndex));
171173
case BYTES:
174+
case JSON:
172175
case DATE:
173176
case STRUCT:
174177
case TIMESTAMP:
@@ -198,6 +201,7 @@ public byte getByte(int columnIndex) throws SQLException {
198201
case STRING:
199202
return isNull ? (byte) 0 : checkedCastToByte(parseLong(spanner.getString(spannerIndex)));
200203
case BYTES:
204+
case JSON:
201205
case DATE:
202206
case STRUCT:
203207
case TIMESTAMP:
@@ -227,6 +231,7 @@ public short getShort(int columnIndex) throws SQLException {
227231
case STRING:
228232
return isNull ? 0 : checkedCastToShort(parseLong(spanner.getString(spannerIndex)));
229233
case BYTES:
234+
case JSON:
230235
case DATE:
231236
case STRUCT:
232237
case TIMESTAMP:
@@ -256,6 +261,7 @@ public int getInt(int columnIndex) throws SQLException {
256261
case STRING:
257262
return isNull ? 0 : checkedCastToInt(parseLong(spanner.getString(spannerIndex)));
258263
case BYTES:
264+
case JSON:
259265
case DATE:
260266
case STRUCT:
261267
case TIMESTAMP:
@@ -283,6 +289,7 @@ public long getLong(int columnIndex) throws SQLException {
283289
case STRING:
284290
return isNull ? 0L : parseLong(spanner.getString(spannerIndex));
285291
case BYTES:
292+
case JSON:
286293
case DATE:
287294
case STRUCT:
288295
case TIMESTAMP:
@@ -310,6 +317,7 @@ public float getFloat(int columnIndex) throws SQLException {
310317
case STRING:
311318
return isNull ? 0 : checkedCastToFloat(parseDouble(spanner.getString(spannerIndex)));
312319
case BYTES:
320+
case JSON:
313321
case DATE:
314322
case STRUCT:
315323
case TIMESTAMP:
@@ -337,6 +345,7 @@ public double getDouble(int columnIndex) throws SQLException {
337345
case STRING:
338346
return isNull ? 0 : parseDouble(spanner.getString(spannerIndex));
339347
case BYTES:
348+
case JSON:
340349
case DATE:
341350
case STRUCT:
342351
case TIMESTAMP:
@@ -372,6 +381,7 @@ public Date getDate(int columnIndex) throws SQLException {
372381
case INT64:
373382
case NUMERIC:
374383
case BYTES:
384+
case JSON:
375385
case STRUCT:
376386
case ARRAY:
377387
default:
@@ -396,6 +406,7 @@ public Time getTime(int columnIndex) throws SQLException {
396406
case INT64:
397407
case NUMERIC:
398408
case BYTES:
409+
case JSON:
399410
case STRUCT:
400411
case ARRAY:
401412
default:
@@ -421,6 +432,7 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException {
421432
case INT64:
422433
case NUMERIC:
423434
case BYTES:
435+
case JSON:
424436
case STRUCT:
425437
case ARRAY:
426438
default:
@@ -576,6 +588,7 @@ private Object getObject(Type type, int columnIndex) throws SQLException {
576588
if (type == Type.int64()) return getLong(columnIndex);
577589
if (type == Type.numeric()) return getBigDecimal(columnIndex);
578590
if (type == Type.string()) return getString(columnIndex);
591+
if (type == Type.json()) return getString(columnIndex);
579592
if (type == Type.timestamp()) return getTimestamp(columnIndex);
580593
if (type.getCode() == Code.ARRAY) return getArray(columnIndex);
581594
throw JdbcSqlExceptionFactory.of(
@@ -664,6 +677,7 @@ private BigDecimal getBigDecimal(int columnIndex, boolean fixedScale, int scale)
664677
e);
665678
}
666679
case BYTES:
680+
case JSON:
667681
case DATE:
668682
case TIMESTAMP:
669683
case STRUCT:
@@ -749,6 +763,7 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException {
749763
case INT64:
750764
case NUMERIC:
751765
case BYTES:
766+
case JSON:
752767
case STRUCT:
753768
case ARRAY:
754769
default:
@@ -778,6 +793,7 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException {
778793
case INT64:
779794
case NUMERIC:
780795
case BYTES:
796+
case JSON:
781797
case STRUCT:
782798
case ARRAY:
783799
default:
@@ -810,6 +826,7 @@ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException
810826
case INT64:
811827
case NUMERIC:
812828
case BYTES:
829+
case JSON:
813830
case STRUCT:
814831
case ARRAY:
815832
default:
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ static Object convert(Object value, Type type, Class<?> targetType) throws SQLEx
8181
}
8282
if (targetType.equals(byte[].class)) {
8383
if (type.getCode() == Code.BYTES) return value;
84-
if (type.getCode() == Code.STRING) return ((String) value).getBytes(UTF8);
84+
if (type.getCode() == Code.STRING || type.getCode() == Code.JSON)
85+
return ((String) value).getBytes(UTF8);
8586
}
8687
if (targetType.equals(Boolean.class)) {
8788
if (type.getCode() == Code.BOOL) return value;
@@ -186,6 +187,8 @@ private static Value convertToSpannerValue(Object value, Type type) throws SQLEx
186187
case TIMESTAMP:
187188
return Value.timestampArray(
188189
toGoogleTimestamps((java.sql.Timestamp[]) ((java.sql.Array) value).getArray()));
190+
case JSON:
191+
return Value.jsonArray(Arrays.asList((String[]) ((java.sql.Array) value).getArray()));
189192
case STRUCT:
190193
default:
191194
throw JdbcSqlExceptionFactory.of(
@@ -207,6 +210,8 @@ private static Value convertToSpannerValue(Object value, Type type) throws SQLEx
207210
return Value.string((String) value);
208211
case TIMESTAMP:
209212
return Value.timestamp(toGoogleTimestamp((java.sql.Timestamp) value));
213+
case JSON:
214+
return Value.json((String) value);
210215
case STRUCT:
211216
default:
212217
throw JdbcSqlExceptionFactory.of(

0 commit comments

Comments
 (0)