Skip to content
Permalink
Browse files
feat: add support for JSON data type (#872)
Allow users to read and write to Cloud Spanner databases using the JSON type through the client libraries.

Integration tests here: zoercai#1
  • Loading branch information
zoercai committed Aug 24, 2021
1 parent c6c9304 commit d7ff9409e974602dc9b18f82d6dbd11d96c956bf
Showing with 927 additions and 57 deletions.
  1. +42 −0 google-cloud-spanner/clirr-ignored-differences.xml
  2. +29 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
  3. +34 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
  4. +24 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
  5. +2 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/Key.java
  6. +20 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
  7. +14 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
  8. +20 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
  9. +12 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
  10. +94 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
  11. +5 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
  12. +10 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java
  13. +24 −0 google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
  14. +24 −0 ...oud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
  15. +24 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
  16. +39 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java
  17. +16 −2 google-cloud-spanner/src/test/java/com/google/cloud/spanner/KeyTest.java
  18. +17 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
  19. +6 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReadFormatTestRunner.java
  20. +60 −40 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java
  21. +21 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java
  22. +31 −4 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java
  23. +142 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java
  24. +9 −0 ...e-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DirectExecuteResultSetTest.java
  25. +8 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java
  26. +32 −4 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java
  27. +9 −0 ...spanner/src/test/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSetTest.java
  28. +65 −0 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java
  29. +84 −6 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java
  30. +10 −1 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/read_tests.json
@@ -659,4 +659,46 @@
<method>void setOptimizerStatisticsPackage(java.lang.String)</method>
</difference>

<!-- Add support for JSON data type -->
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/AbstractStructReader</className>
<method>java.lang.String getJsonInternal(int)</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/AbstractStructReader</className>
<method>java.util.List getJsonListInternal(int)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.lang.String getJson(int)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.lang.String getJson(java.lang.String)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.util.List getJsonList(int)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.util.List getJsonList(java.lang.String)</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/Value</className>
<method>java.lang.String getJson()</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/Value</className>
<method>java.util.List getJsonArray()</method>
</difference>

</differences>
@@ -378,6 +378,9 @@ private Object writeReplace() {
case STRING:
builder.set(fieldName).to((String) value);
break;
case JSON:
builder.set(fieldName).to(Value.json((String) value));
break;
case BYTES:
builder.set(fieldName).to((ByteArray) value);
break;
@@ -404,6 +407,9 @@ private Object writeReplace() {
case STRING:
builder.set(fieldName).toStringArray((Iterable<String>) value);
break;
case JSON:
builder.set(fieldName).toJsonArray((Iterable<String>) value);
break;
case BYTES:
builder.set(fieldName).toBytesArray((Iterable<ByteArray>) value);
break;
@@ -480,6 +486,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
case NUMERIC:
return new BigDecimal(proto.getStringValue());
case STRING:
case JSON:
checkType(fieldType, proto, KindCase.STRING_VALUE);
return proto.getStringValue();
case BYTES:
@@ -543,6 +550,7 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) {
return list;
}
case STRING:
case JSON:
return Lists.transform(
listValue.getValuesList(),
input -> input.getKindCase() == KindCase.NULL_VALUE ? null : input.getStringValue());
@@ -654,6 +662,11 @@ protected String getStringInternal(int columnIndex) {
return (String) rowData.get(columnIndex);
}

@Override
protected String getJsonInternal(int columnIndex) {
return (String) rowData.get(columnIndex);
}

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return (ByteArray) rowData.get(columnIndex);
@@ -782,6 +795,12 @@ protected List<String> getStringListInternal(int columnIndex) {
return Collections.unmodifiableList((List<String>) rowData.get(columnIndex));
}

@Override
@SuppressWarnings("unchecked") // We know ARRAY<String> produces a List<String>.
protected List<String> getJsonListInternal(int columnIndex) {
return Collections.unmodifiableList((List<String>) rowData.get(columnIndex));
}

@Override
@SuppressWarnings("unchecked") // We know ARRAY<BYTES> produces a List<ByteArray>.
protected List<ByteArray> getBytesListInternal(int columnIndex) {
@@ -1308,6 +1327,11 @@ protected String getStringInternal(int columnIndex) {
return currRow().getStringInternal(columnIndex);
}

@Override
protected String getJsonInternal(int columnIndex) {
return currRow().getJsonInternal(columnIndex);
}

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return currRow().getBytesInternal(columnIndex);
@@ -1368,6 +1392,11 @@ protected List<String> getStringListInternal(int columnIndex) {
return currRow().getStringListInternal(columnIndex);
}

@Override
protected List<String> getJsonListInternal(int columnIndex) {
return currRow().getJsonListInternal(columnIndex);
}

@Override
protected List<ByteArray> getBytesListInternal(int columnIndex) {
return currRow().getBytesListInternal(columnIndex);
@@ -43,6 +43,10 @@ public abstract class AbstractStructReader implements StructReader {

protected abstract String getStringInternal(int columnIndex);

protected String getJsonInternal(int columnIndex) {
throw new UnsupportedOperationException("Not implemented");
}

protected abstract ByteArray getBytesInternal(int columnIndex);

protected abstract Timestamp getTimestampInternal(int columnIndex);
@@ -69,6 +73,10 @@ protected Value getValueInternal(int columnIndex) {

protected abstract List<String> getStringListInternal(int columnIndex);

protected List<String> getJsonListInternal(int columnIndex) {
throw new UnsupportedOperationException("Not implemented");
}

protected abstract List<ByteArray> getBytesListInternal(int columnIndex);

protected abstract List<Timestamp> getTimestampListInternal(int columnIndex);
@@ -162,6 +170,19 @@ public String getString(String columnName) {
return getStringInternal(columnIndex);
}

@Override
public String getJson(int columnIndex) {
checkNonNullOfType(columnIndex, Type.json(), columnIndex);
return getJsonInternal(columnIndex);
}

@Override
public String getJson(String columnName) {
int columnIndex = getColumnIndex(columnName);
checkNonNullOfType(columnIndex, Type.json(), columnName);
return getJsonInternal(columnIndex);
}

@Override
public ByteArray getBytes(int columnIndex) {
checkNonNullOfType(columnIndex, Type.bytes(), columnIndex);
@@ -317,6 +338,19 @@ public List<String> getStringList(String columnName) {
return getStringListInternal(columnIndex);
}

@Override
public List<String> getJsonList(int columnIndex) {
checkNonNullOfType(columnIndex, Type.array(Type.json()), columnIndex);
return getJsonListInternal(columnIndex);
}

@Override
public List<String> getJsonList(String columnName) {
int columnIndex = getColumnIndex(columnName);
checkNonNullOfType(columnIndex, Type.array(Type.json()), columnName);
return getJsonListInternal(columnIndex);
}

@Override
public List<ByteArray> getBytesList(int columnIndex) {
checkNonNullOfType(columnIndex, Type.array(Type.bytes()), columnIndex);
@@ -156,6 +156,18 @@ public String getString(String columnName) {
return delegate.get().getString(columnName);
}

@Override
public String getJson(int columnIndex) {
checkValidState();
return delegate.get().getJson(columnIndex);
}

@Override
public String getJson(String columnName) {
checkValidState();
return delegate.get().getJson(columnName);
}

@Override
public ByteArray getBytes(int columnIndex) {
checkValidState();
@@ -286,6 +298,18 @@ public List<String> getStringList(String columnName) {
return delegate.get().getStringList(columnName);
}

@Override
public List<String> getJsonList(int columnIndex) {
checkValidState();
return delegate.get().getJsonList(columnIndex);
}

@Override
public List<String> getJsonList(String columnName) {
checkValidState();
return delegate.get().getJsonList(columnName);
}

@Override
public List<ByteArray> getBytesList(int columnIndex) {
checkValidState();
@@ -65,6 +65,7 @@ private Key(List<Object> parts) {
* <li>{@code Float}, {@code Double} for the {@code FLOAT64} Cloud Spanner type
* <li>{@code BigDecimal} for the {@code NUMERIC} Cloud Spanner type
* <li>{@code String} for the {@code STRING} Cloud Spanner type
* <li>{@code String} for the {@code JSON} Cloud Spanner type
* <li>{@link ByteArray} for the {@code BYTES} Cloud Spanner type
* <li>{@link Timestamp} for the {@code TIMESTAMP} Cloud Spanner type
* <li>{@link Date} for the {@code DATE} Cloud Spanner type
@@ -228,6 +229,7 @@ public int size() {
* <li>{@code FLOAT64} is represented by {@code Double}
* <li>{@code NUMERIC} is represented by {@code BigDecimal}
* <li>{@code STRING} is represented by {@code String}
* <li>{@code JSON} is represented by {@code String}
* <li>{@code BYTES} is represented by {@link ByteArray}
* <li>{@code TIMESTAMP} is represented by {@link Timestamp}
* <li>{@code DATE} is represented by {@link Date}
@@ -243,6 +243,16 @@ public String getString(String columnName) {
return getCurrentRowAsStruct().getString(columnName);
}

@Override
public String getJson(int columnIndex) {
return getCurrentRowAsStruct().getJson(columnIndex);
}

@Override
public String getJson(String columnName) {
return getCurrentRowAsStruct().getJson(columnName);
}

@Override
public ByteArray getBytes(int columnIndex) {
return getCurrentRowAsStruct().getBytes(columnIndex);
@@ -363,6 +373,16 @@ public List<String> getStringList(String columnName) {
return getCurrentRowAsStruct().getStringList(columnName);
}

@Override
public List<String> getJsonList(int columnIndex) {
return getCurrentRowAsStruct().getJsonList(columnIndex);
}

@Override
public List<String> getJsonList(String columnName) {
return getCurrentRowAsStruct().getJsonList(columnName);
}

@Override
public List<ByteArray> getBytesList(int columnIndex) {
return getCurrentRowAsStruct().getBytesList(columnIndex);
@@ -192,6 +192,11 @@ protected String getStringInternal(int columnIndex) {
return values.get(columnIndex).getString();
}

@Override
protected String getJsonInternal(int columnIndex) {
return values.get(columnIndex).getJson();
}

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return values.get(columnIndex).getBytes();
@@ -257,6 +262,11 @@ protected List<String> getStringListInternal(int columnIndex) {
return values.get(columnIndex).getStringArray();
}

@Override
protected List<String> getJsonListInternal(int columnIndex) {
return values.get(columnIndex).getJsonArray();
}

@Override
protected List<ByteArray> getBytesListInternal(int columnIndex) {
return values.get(columnIndex).getBytesArray();
@@ -341,6 +351,8 @@ private Object getAsObject(int columnIndex) {
return getBigDecimalInternal(columnIndex);
case STRING:
return getStringInternal(columnIndex);
case JSON:
return getJsonInternal(columnIndex);
case BYTES:
return getBytesInternal(columnIndex);
case TIMESTAMP:
@@ -361,6 +373,8 @@ private Object getAsObject(int columnIndex) {
return getBigDecimalListInternal(columnIndex);
case STRING:
return getStringListInternal(columnIndex);
case JSON:
return getJsonListInternal(columnIndex);
case BYTES:
return getBytesListInternal(columnIndex);
case TIMESTAMP:
@@ -114,6 +114,16 @@ public interface StructReader {
/** Returns the value of a non-{@code NULL} column with type {@link Type#string()}. */
String getString(String columnName);

/** Returns the value of a non-{@code NULL} column with type {@link Type#string()}. */
default String getJson(int columnIndex) {
throw new UnsupportedOperationException("method should be overwritten");
}

/** Returns the value of a non-{@code NULL} column with type {@link Type#string()}. */
default String getJson(String columnName) {
throw new UnsupportedOperationException("method should be overwritten");
}

/** Returns the value of a non-{@code NULL} column with type {@link Type#bytes()}. */
ByteArray getBytes(int columnIndex);

@@ -228,6 +238,16 @@ default Value getValue(String columnName) {
/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.string())}. */
List<String> getStringList(String columnName);

/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.string())}. */
default List<String> getJsonList(int columnIndex) {
throw new UnsupportedOperationException("method should be overwritten");
};

/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.string())}. */
default List<String> getJsonList(String columnName) {
throw new UnsupportedOperationException("method should be overwritten");
};

/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.bytes())}. */
List<ByteArray> getBytesList(int columnIndex);

0 comments on commit d7ff940

Please sign in to comment.