Skip to content

Commit

Permalink
feat: add support for PG JSONB data type (#1964)
Browse files Browse the repository at this point in the history
* feat: add support for PG JSONB data type

* chore: address review comments

* test: temporarily disable integration test for JSONB
  • Loading branch information
olavloite committed Aug 29, 2022
1 parent e93ab4c commit d2b426f
Show file tree
Hide file tree
Showing 24 changed files with 1,371 additions and 24 deletions.
21 changes: 21 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Expand Up @@ -131,4 +131,25 @@
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
<method>com.google.iam.v1.Policy getDatabaseAdminIAMPolicy(java.lang.String)</method>
</difference>
<!-- PG JSONB -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.lang.String getPgJsonb(int)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.lang.String getPgJsonb(java.lang.String)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.util.List getPgJsonbList(int)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/StructReader</className>
<method>java.util.List getPgJsonbList(java.lang.String)</method>
</difference>
</differences>
Expand Up @@ -67,6 +67,7 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/** Implementation of {@link ResultSet}. */
Expand Down Expand Up @@ -384,6 +385,9 @@ private Object writeReplace() {
case JSON:
builder.set(fieldName).to(Value.json((String) value));
break;
case PG_JSONB:
builder.set(fieldName).to(Value.pgJsonb((String) value));
break;
case BYTES:
builder.set(fieldName).to((ByteArray) value);
break;
Expand Down Expand Up @@ -417,6 +421,9 @@ private Object writeReplace() {
case JSON:
builder.set(fieldName).toJsonArray((Iterable<String>) value);
break;
case PG_JSONB:
builder.set(fieldName).toPgJsonbArray((Iterable<String>) value);
break;
case BYTES:
builder.set(fieldName).toBytesArray((Iterable<ByteArray>) value);
break;
Expand Down Expand Up @@ -491,10 +498,9 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
checkType(fieldType, proto, KindCase.STRING_VALUE);
return new BigDecimal(proto.getStringValue());
case PG_NUMERIC:
checkType(fieldType, proto, KindCase.STRING_VALUE);
return proto.getStringValue();
case STRING:
case JSON:
case PG_JSONB:
checkType(fieldType, proto, KindCase.STRING_VALUE);
return proto.getStringValue();
case BYTES:
Expand Down Expand Up @@ -558,14 +564,14 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) {
return list;
}
case PG_NUMERIC:
return Lists.transform(
listValue.getValuesList(),
input -> input.getKindCase() == KindCase.NULL_VALUE ? null : input.getStringValue());
case STRING:
case JSON:
return Lists.transform(
listValue.getValuesList(),
input -> input.getKindCase() == KindCase.NULL_VALUE ? null : input.getStringValue());
case PG_JSONB:
return listValue.getValuesList().stream()
.map(
input ->
input.getKindCase() == KindCase.NULL_VALUE ? null : input.getStringValue())
.collect(Collectors.toList());
case BYTES:
{
// Materialize list: element conversion is expensive and should happen only once.
Expand Down Expand Up @@ -679,6 +685,11 @@ protected String getJsonInternal(int columnIndex) {
return (String) rowData.get(columnIndex);
}

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

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return (ByteArray) rowData.get(columnIndex);
Expand Down Expand Up @@ -715,6 +726,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.string(isNull ? null : getStringInternal(columnIndex));
case JSON:
return Value.json(isNull ? null : getJsonInternal(columnIndex));
case PG_JSONB:
return Value.pgJsonb(isNull ? null : getPgJsonbInternal(columnIndex));
case BYTES:
return Value.bytes(isNull ? null : getBytesInternal(columnIndex));
case TIMESTAMP:
Expand All @@ -740,6 +753,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.stringArray(isNull ? null : getStringListInternal(columnIndex));
case JSON:
return Value.jsonArray(isNull ? null : getJsonListInternal(columnIndex));
case PG_JSONB:
return Value.pgJsonbArray(isNull ? null : getPgJsonbListInternal(columnIndex));
case BYTES:
return Value.bytesArray(isNull ? null : getBytesListInternal(columnIndex));
case TIMESTAMP:
Expand Down Expand Up @@ -816,11 +831,17 @@ protected List<String> getStringListInternal(int columnIndex) {
}

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

@Override
@SuppressWarnings("unchecked") // We know ARRAY<JSONB> produces a List<String>.
protected List<String> getPgJsonbListInternal(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 @@ -1352,6 +1373,11 @@ protected String getJsonInternal(int columnIndex) {
return currRow().getJsonInternal(columnIndex);
}

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

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return currRow().getBytesInternal(columnIndex);
Expand Down Expand Up @@ -1417,6 +1443,11 @@ protected List<String> getJsonListInternal(int columnIndex) {
return currRow().getJsonListInternal(columnIndex);
}

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

@Override
protected List<ByteArray> getBytesListInternal(int columnIndex) {
return currRow().getBytesListInternal(columnIndex);
Expand Down
Expand Up @@ -48,6 +48,10 @@ protected String getJsonInternal(int columnIndex) {
throw new UnsupportedOperationException("Not implemented");
}

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

protected abstract ByteArray getBytesInternal(int columnIndex);

protected abstract Timestamp getTimestampInternal(int columnIndex);
Expand Down Expand Up @@ -78,6 +82,10 @@ protected List<String> getJsonListInternal(int columnIndex) {
throw new UnsupportedOperationException("Not implemented");
}

protected List<String> getPgJsonbListInternal(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 @@ -189,6 +197,19 @@ public String getJson(String columnName) {
return getJsonInternal(columnIndex);
}

@Override
public String getPgJsonb(int columnIndex) {
checkNonNullOfType(columnIndex, Type.pgJsonb(), columnIndex);
return getPgJsonbInternal(columnIndex);
}

@Override
public String getPgJsonb(String columnName) {
int columnIndex = getColumnIndex(columnName);
checkNonNullOfType(columnIndex, Type.pgJsonb(), columnName);
return getPgJsonbInternal(columnIndex);
}

@Override
public ByteArray getBytes(int columnIndex) {
checkNonNullOfType(columnIndex, Type.bytes(), columnIndex);
Expand Down Expand Up @@ -365,6 +386,19 @@ public List<String> getJsonList(String columnName) {
return getJsonListInternal(columnIndex);
}

@Override
public List<String> getPgJsonbList(int columnIndex) {
checkNonNullOfType(columnIndex, Type.array(Type.pgJsonb()), columnIndex);
return getPgJsonbListInternal(columnIndex);
}

@Override
public List<String> getPgJsonbList(String columnName) {
int columnIndex = getColumnIndex(columnName);
checkNonNullOfType(columnIndex, Type.array(Type.pgJsonb()), columnName);
return getPgJsonbListInternal(columnIndex);
}

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

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

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

@Override
public ByteArray getBytes(int columnIndex) {
checkValidState();
Expand Down Expand Up @@ -310,6 +322,18 @@ public List<String> getJsonList(String columnName) {
return delegate.get().getJsonList(columnName);
}

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

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

@Override
public List<ByteArray> getBytesList(int columnIndex) {
checkValidState();
Expand Down
Expand Up @@ -253,6 +253,16 @@ public String getJson(String columnName) {
return getCurrentRowAsStruct().getJson(columnName);
}

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

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

@Override
public ByteArray getBytes(int columnIndex) {
return getCurrentRowAsStruct().getBytes(columnIndex);
Expand Down Expand Up @@ -383,6 +393,16 @@ public List<String> getJsonList(String columnName) {
return getCurrentRowAsStruct().getJsonList(columnName);
}

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

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

@Override
public List<ByteArray> getBytesList(int columnIndex) {
return getCurrentRowAsStruct().getBytesList(columnIndex);
Expand Down
Expand Up @@ -197,6 +197,11 @@ protected String getJsonInternal(int columnIndex) {
return values.get(columnIndex).getJson();
}

@Override
protected String getPgJsonbInternal(int columnIndex) {
return values.get(columnIndex).getPgJsonb();
}

@Override
protected ByteArray getBytesInternal(int columnIndex) {
return values.get(columnIndex).getBytes();
Expand Down Expand Up @@ -267,6 +272,11 @@ protected List<String> getJsonListInternal(int columnIndex) {
return values.get(columnIndex).getJsonArray();
}

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

@Override
protected List<ByteArray> getBytesListInternal(int columnIndex) {
return values.get(columnIndex).getBytesArray();
Expand Down Expand Up @@ -355,6 +365,8 @@ private Object getAsObject(int columnIndex) {
return getStringInternal(columnIndex);
case JSON:
return getJsonInternal(columnIndex);
case PG_JSONB:
return getPgJsonbInternal(columnIndex);
case BYTES:
return getBytesInternal(columnIndex);
case TIMESTAMP:
Expand All @@ -379,6 +391,8 @@ private Object getAsObject(int columnIndex) {
return getStringListInternal(columnIndex);
case JSON:
return getJsonListInternal(columnIndex);
case PG_JSONB:
return getPgJsonbListInternal(columnIndex);
case BYTES:
return getBytesListInternal(columnIndex);
case TIMESTAMP:
Expand Down
Expand Up @@ -114,16 +114,26 @@ 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()}. */
/** Returns the value of a non-{@code NULL} column with type {@link Type#json()}. */
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()}. */
/** Returns the value of a non-{@code NULL} column with type {@link Type#json()}. */
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#pgJsonb()}. */
default String getPgJsonb(int columnIndex) {
throw new UnsupportedOperationException("method should be overwritten");
}

/** Returns the value of a non-{@code NULL} column with type {@link Type#pgJsonb()}. */
default String getPgJsonb(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 @@ -238,16 +248,30 @@ 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())}. */
/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.json())}. */
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())}. */
/** Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.json())}. */
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.pgJsonb())}.
*/
default List<String> getPgJsonbList(int columnIndex) {
throw new UnsupportedOperationException("method should be overwritten");
};

/**
* Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.pgJsonb())}.
*/
default List<String> getPgJsonbList(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 d2b426f

Please sign in to comment.