Skip to content

Commit

Permalink
feat: add support for JSON data type (#872)
Browse files Browse the repository at this point in the history
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 d7ff940
Show file tree
Hide file tree
Showing 30 changed files with 927 additions and 57 deletions.
42 changes: 42 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Expand Up @@ -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>
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit d7ff940

Please sign in to comment.