diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 637ca06280a..8e4710354e3 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -192,6 +192,46 @@ com/google/cloud/spanner/StructReader java.util.List getPgJsonbList(java.lang.String) + + 7012 + com/google/cloud/spanner/StructReader + com.google.protobuf.ProtocolMessageEnum getProtoEnum(int, java.util.function.Function) + + + 7012 + com/google/cloud/spanner/StructReader + com.google.protobuf.ProtocolMessageEnum getProtoEnum(java.lang.String, java.util.function.Function) + + + 7012 + com/google/cloud/spanner/StructReader + com.google.protobuf.AbstractMessage getProtoMessage(int, com.google.protobuf.AbstractMessage) + + + 7012 + com/google/cloud/spanner/StructReader + com.google.protobuf.AbstractMessage getProtoMessage(java.lang.String, com.google.protobuf.AbstractMessage) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getProtoEnumList(int, java.util.function.Function) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getProtoEnumList(java.lang.String, java.util.function.Function) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getProtoMessageList(int, com.google.protobuf.AbstractMessage) + + + 7012 + com/google/cloud/spanner/StructReader + java.util.List getProtoMessageList(java.lang.String, com.google.protobuf.AbstractMessage) + 7012 com/google/cloud/spanner/BatchClient diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java index 789e0945e17..938668c74c0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java @@ -32,12 +32,16 @@ import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.cloud.spanner.v1.stub.SpannerStubSettings; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Uninterruptibles; +import com.google.protobuf.AbstractMessage; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; +import com.google.protobuf.ProtocolMessageEnum; import com.google.protobuf.Value.KindCase; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.ResultSetMetadata; @@ -65,6 +69,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -391,6 +396,14 @@ private Object writeReplace() { case JSON: builder.set(fieldName).to(Value.json((String) value)); break; + case PROTO: + builder + .set(fieldName) + .to(Value.protoMessage((ByteArray) value, fieldType.getProtoTypeFqn())); + break; + case ENUM: + builder.set(fieldName).to(Value.protoEnum((Long) value, fieldType.getProtoTypeFqn())); + break; case PG_JSONB: builder.set(fieldName).to(Value.pgJsonb((String) value)); break; @@ -410,6 +423,7 @@ private Object writeReplace() { builder.set(fieldName).toBoolArray((Iterable) value); break; case INT64: + case ENUM: builder.set(fieldName).toInt64Array((Iterable) value); break; case FLOAT64: @@ -431,6 +445,7 @@ private Object writeReplace() { builder.set(fieldName).toPgJsonbArray((Iterable) value); break; case BYTES: + case PROTO: builder.set(fieldName).toBytesArray((Iterable) value); break; case TIMESTAMP: @@ -496,6 +511,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot checkType(fieldType, proto, KindCase.BOOL_VALUE); return proto.getBoolValue(); case INT64: + case ENUM: checkType(fieldType, proto, KindCase.STRING_VALUE); return Long.parseLong(proto.getStringValue()); case FLOAT64: @@ -510,6 +526,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot checkType(fieldType, proto, KindCase.STRING_VALUE); return proto.getStringValue(); case BYTES: + case PROTO: checkType(fieldType, proto, KindCase.STRING_VALUE); return ByteArray.fromBase64(proto.getStringValue()); case TIMESTAMP: @@ -547,7 +564,8 @@ private static Struct decodeStructValue(Type structType, ListValue structValue) static Object decodeArrayValue(Type elementType, ListValue listValue) { switch (elementType.getCode()) { case INT64: - // For int64/float64 types, use custom containers. These avoid wrapper object + case ENUM: + // For int64/float64/enum types, use custom containers. These avoid wrapper object // creation for non-null arrays. return new Int64Array(listValue); case FLOAT64: @@ -562,6 +580,7 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) { case TIMESTAMP: case DATE: case STRUCT: + case PROTO: return Lists.transform( listValue.getValuesList(), input -> decodeValue(elementType, input)); default: @@ -597,6 +616,30 @@ public boolean isNull(int columnIndex) { return rowData.get(columnIndex) == null; } + @Override + protected T getProtoMessageInternal(int columnIndex, T message) { + Preconditions.checkNotNull( + message, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + try { + return (T) + message + .toBuilder() + .mergeFrom(((ByteArray) rowData.get(columnIndex)).toByteArray()) + .build(); + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + + @Override + protected T getProtoEnumInternal( + int columnIndex, Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + return (T) method.apply((int) getLongInternal(columnIndex)); + } + @Override protected boolean getBooleanInternal(int columnIndex) { return (Boolean) rowData.get(columnIndex); @@ -658,6 +701,8 @@ protected Value getValueInternal(int columnIndex) { return Value.bool(isNull ? null : getBooleanInternal(columnIndex)); case INT64: return Value.int64(isNull ? null : getLongInternal(columnIndex)); + case ENUM: + return Value.protoEnum(getLongInternal(columnIndex), columnType.getProtoTypeFqn()); case NUMERIC: return Value.numeric(isNull ? null : getBigDecimalInternal(columnIndex)); case PG_NUMERIC: @@ -672,6 +717,8 @@ protected Value getValueInternal(int columnIndex) { return Value.pgJsonb(isNull ? null : getPgJsonbInternal(columnIndex)); case BYTES: return Value.bytes(isNull ? null : getBytesInternal(columnIndex)); + case PROTO: + return Value.protoMessage(getBytesInternal(columnIndex), columnType.getProtoTypeFqn()); case TIMESTAMP: return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex)); case DATE: @@ -699,6 +746,12 @@ protected Value getValueInternal(int columnIndex) { return Value.pgJsonbArray(isNull ? null : getPgJsonbListInternal(columnIndex)); case BYTES: return Value.bytesArray(isNull ? null : getBytesListInternal(columnIndex)); + case PROTO: + return Value.protoMessageArray( + isNull ? null : getBytesListInternal(columnIndex), elementType.getProtoTypeFqn()); + case ENUM: + return Value.protoEnumArray( + isNull ? null : getLongListInternal(columnIndex), elementType.getProtoTypeFqn()); case TIMESTAMP: return Value.timestampArray(isNull ? null : getTimestampListInternal(columnIndex)); case DATE: @@ -778,6 +831,52 @@ protected List getJsonListInternal(int columnIndex) { return Collections.unmodifiableList((List) rowData.get(columnIndex)); } + @Override + @SuppressWarnings("unchecked") // We know ARRAY produces a List. + protected List getProtoMessageListInternal( + int columnIndex, T message) { + Preconditions.checkNotNull( + message, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + + List bytesArray = (List) rowData.get(columnIndex); + + try { + List protoMessagesList = new ArrayList<>(bytesArray.size()); + for (ByteArray protoMessageBytes : bytesArray) { + if (protoMessageBytes == null) { + protoMessagesList.add(null); + } else { + protoMessagesList.add( + (T) message.toBuilder().mergeFrom(protoMessageBytes.toByteArray()).build()); + } + } + return protoMessagesList; + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + + @Override + @SuppressWarnings("unchecked") // We know ARRAY produces a List. + protected List getProtoEnumListInternal( + int columnIndex, Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + + List enumIntArray = (List) rowData.get(columnIndex); + List protoEnumList = new ArrayList<>(enumIntArray.size()); + for (Long enumIntValue : enumIntArray) { + if (enumIntValue == null) { + protoEnumList.add(null); + } else { + protoEnumList.add((T) method.apply(enumIntValue.intValue())); + } + } + + return protoEnumList; + } + @Override @SuppressWarnings("unchecked") // We know ARRAY produces a List. protected List getPgJsonbListInternal(int columnIndex) { @@ -1310,6 +1409,17 @@ protected String getStringInternal(int columnIndex) { return currRow().getStringInternal(columnIndex); } + @Override + protected T getProtoMessageInternal(int columnIndex, T message) { + return currRow().getProtoMessageInternal(columnIndex, message); + } + + @Override + protected T getProtoEnumInternal( + int columnIndex, Function method) { + return currRow().getProtoEnumInternal(columnIndex, method); + } + @Override protected String getJsonInternal(int columnIndex) { return currRow().getJsonInternal(columnIndex); @@ -1395,6 +1505,18 @@ protected List getBytesListInternal(int columnIndex) { return currRow().getBytesListInternal(columnIndex); } + @Override + protected List getProtoMessageListInternal( + int columnIndex, T message) { + return currRow().getProtoMessageListInternal(columnIndex, message); + } + + @Override + protected List getProtoEnumListInternal( + int columnIndex, Function method) { + return currRow().getProtoEnumListInternal(columnIndex, method); + } + @Override protected List getTimestampListInternal(int columnIndex) { return currRow().getTimestampListInternal(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java index 1e897636245..5c205b967eb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java @@ -21,9 +21,14 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Type.Code; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.math.BigDecimal; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * Base class for assisting {@link StructReader} implementations. @@ -58,6 +63,25 @@ protected String getPgJsonbInternal(int columnIndex) { protected abstract Date getDateInternal(int columnIndex); + protected T getProtoMessageInternal(int columnIndex, T message) { + throw new UnsupportedOperationException("Not implemented"); + } + + protected T getProtoEnumInternal( + int columnIndex, Function method) { + throw new UnsupportedOperationException("Not implemented"); + } + + protected List getProtoMessageListInternal( + int columnIndex, T message) { + throw new UnsupportedOperationException("Not implemented"); + } + + protected List getProtoEnumListInternal( + int columnIndex, Function method) { + throw new UnsupportedOperationException("Not implemented"); + } + protected Value getValueInternal(int columnIndex) { throw new UnsupportedOperationException("method should be overwritten"); } @@ -129,14 +153,14 @@ public boolean getBoolean(String columnName) { @Override public long getLong(int columnIndex) { - checkNonNullOfType(columnIndex, Type.int64(), columnIndex); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); return getLongInternal(columnIndex); } @Override public long getLong(String columnName) { int columnIndex = getColumnIndex(columnName); - checkNonNullOfType(columnIndex, Type.int64(), columnName); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); return getLongInternal(columnIndex); } @@ -212,14 +236,14 @@ public String getPgJsonb(String columnName) { @Override public ByteArray getBytes(int columnIndex) { - checkNonNullOfType(columnIndex, Type.bytes(), columnIndex); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnIndex); return getBytesInternal(columnIndex); } @Override public ByteArray getBytes(String columnName) { int columnIndex = getColumnIndex(columnName); - checkNonNullOfType(columnIndex, Type.bytes(), columnName); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnName); return getBytesInternal(columnIndex); } @@ -249,6 +273,34 @@ public Date getDate(String columnName) { return getDateInternal(columnIndex); } + @Override + public T getProtoEnum( + int columnIndex, Function method) { + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); + return getProtoEnumInternal(columnIndex, method); + } + + @Override + public T getProtoEnum( + String columnName, Function method) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); + return getProtoEnumInternal(columnIndex, method); + } + + @Override + public T getProtoMessage(int columnIndex, T message) { + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnIndex); + return getProtoMessageInternal(columnIndex, message); + } + + @Override + public T getProtoMessage(String columnName, T message) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnName); + return getProtoMessageInternal(columnIndex, message); + } + @Override public Value getValue(int columnIndex) { checkNonNull(columnIndex, columnIndex); @@ -302,14 +354,16 @@ public long[] getLongArray(String columnName) { @Override public List getLongList(int columnIndex) { - checkNonNullOfType(columnIndex, Type.array(Type.int64()), columnIndex); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); + checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); return getLongListInternal(columnIndex); } @Override public List getLongList(String columnName) { int columnIndex = getColumnIndex(columnName); - checkNonNullOfType(columnIndex, Type.array(Type.int64()), columnName); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); + checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); return getLongListInternal(columnIndex); } @@ -401,17 +455,51 @@ public List getPgJsonbList(String columnName) { @Override public List getBytesList(int columnIndex) { - checkNonNullOfType(columnIndex, Type.array(Type.bytes()), columnIndex); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); + checkArrayElementType(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnIndex); return getBytesListInternal(columnIndex); } @Override public List getBytesList(String columnName) { int columnIndex = getColumnIndex(columnName); - checkNonNullOfType(columnIndex, Type.array(Type.bytes()), columnName); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); + checkArrayElementType(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnName); return getBytesListInternal(columnIndex); } + @Override + public List getProtoMessageList(int columnIndex, T message) { + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); + checkArrayElementType(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnIndex); + return getProtoMessageListInternal(columnIndex, message); + } + + @Override + public List getProtoMessageList(String columnName, T message) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); + checkArrayElementType(columnIndex, Arrays.asList(Code.PROTO, Code.BYTES), columnName); + return getProtoMessageListInternal(columnIndex, message); + } + + @Override + public List getProtoEnumList( + int columnIndex, Function method) { + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); + checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); + return getProtoEnumListInternal(columnIndex, method); + } + + @Override + public List getProtoEnumList( + String columnName, Function method) { + int columnIndex = getColumnIndex(columnName); + checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); + checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); + return getProtoEnumListInternal(columnIndex, method); + } + @Override public List getTimestampList(int columnIndex) { checkNonNullOfType(columnIndex, Type.array(Type.timestamp()), columnIndex); @@ -477,6 +565,30 @@ private void checkNonNullOfType(int columnIndex, Type expectedType, Object colum checkNonNull(columnIndex, columnNameForError); } + /** Checks if the value at {@code columnIndex} is one of {@code expectedCode} */ + private void checkNonNullOfCodes( + int columnIndex, List expectedCodes, Object columnNameForError) { + Type actualType = getColumnType(columnIndex); + checkState( + expectedCodes.contains(actualType.getCode()), + "Column %s is not of correct type code: expected one of [%s] but was %s", + columnNameForError, + expectedCodes, + actualType); + checkNonNull(columnIndex, columnNameForError); + } + + private void checkArrayElementType( + int columnIndex, List expectedCodes, Object columnNameForError) { + Type arrayElementType = getColumnType(columnIndex).getArrayElementType(); + checkState( + expectedCodes.contains(arrayElementType.getCode()), + "Array element for Column %s is not of correct type code: expected one of [%s] but was %s", + columnNameForError, + expectedCodes, + Type.array(arrayElementType)); + } + private void checkNonNullOfTypes( int columnIndex, List expectedTypes, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java index 2a85006fa95..97c39c00a8d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java @@ -22,8 +22,11 @@ import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.math.BigDecimal; import java.util.List; +import java.util.function.Function; /** Forwarding implements of StructReader */ public class ForwardingStructReader implements StructReader { @@ -370,6 +373,32 @@ public List getDateList(String columnName) { return delegate.get().getDateList(columnName); } + @Override + public List getProtoMessageList(int columnIndex, T message) { + checkValidState(); + return delegate.get().getProtoMessageList(columnIndex, message); + } + + @Override + public List getProtoMessageList(String columnName, T message) { + checkValidState(); + return delegate.get().getProtoMessageList(columnName, message); + } + + @Override + public List getProtoEnumList( + int columnIndex, Function method) { + checkValidState(); + return delegate.get().getProtoEnumList(columnIndex, method); + } + + @Override + public List getProtoEnumList( + String columnName, Function method) { + checkValidState(); + return delegate.get().getProtoEnumList(columnName, method); + } + @Override public List getStructList(int columnIndex) { checkValidState(); @@ -382,6 +411,32 @@ public List getStructList(String columnName) { return delegate.get().getStructList(columnName); } + @Override + public T getProtoMessage(int columnIndex, T message) { + checkValidState(); + return delegate.get().getProtoMessage(columnIndex, message); + } + + @Override + public T getProtoMessage(String columnName, T message) { + checkValidState(); + return delegate.get().getProtoMessage(columnName, message); + } + + @Override + public T getProtoEnum( + int columnIndex, Function method) { + checkValidState(); + return delegate.get().getProtoEnum(columnIndex, method); + } + + @Override + public T getProtoEnum( + String columnName, Function method) { + checkValidState(); + return delegate.get().getProtoEnum(columnName, method); + } + @Override public Value getValue(int columnIndex) { checkValidState(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Key.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Key.java index 15d4e995bf0..3467052605a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Key.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Key.java @@ -24,6 +24,7 @@ import com.google.common.base.Joiner; import com.google.protobuf.ListValue; import com.google.protobuf.NullValue; +import com.google.protobuf.ProtocolMessageEnum; import com.google.protobuf.Value; import java.io.Serializable; import java.math.BigDecimal; @@ -141,6 +142,11 @@ public Builder append(@Nullable BigDecimal value) { buffer.add(value); return this; } + /** Appends a {@code ENUM} value to the key. */ + public Builder append(@Nullable ProtocolMessageEnum value) { + buffer.add(value); + return this; + } /** Appends a {@code STRING} value to the key. */ public Builder append(@Nullable String value) { buffer.add(value); @@ -192,6 +198,8 @@ public Builder appendObject(@Nullable Object value) { append((Timestamp) value); } else if (value instanceof Date) { append((Date) value); + } else if (value instanceof ProtocolMessageEnum) { + append((ProtocolMessageEnum) value); } else { throw new IllegalArgumentException( "Unsupported type [" @@ -300,6 +308,10 @@ ListValue toProto() { builder.addValuesBuilder().setStringValue(part.toString()); } else if (part instanceof Date) { builder.addValuesBuilder().setStringValue(part.toString()); + } else if (part instanceof ProtocolMessageEnum) { + builder + .addValuesBuilder() + .setStringValue(Long.toString(((ProtocolMessageEnum) part).getNumber())); } else { throw new AssertionError("Illegal key part: " + part.getClass()); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java index fa054ba0cda..d55d4091b9f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java @@ -29,10 +29,13 @@ import com.google.common.base.Supplier; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import com.google.spanner.v1.ResultSetMetadata; import com.google.spanner.v1.ResultSetStats; import java.math.BigDecimal; import java.util.List; +import java.util.function.Function; /** Utility methods for working with {@link com.google.cloud.spanner.ResultSet}. */ public final class ResultSets { @@ -300,6 +303,28 @@ public Date getDate(String columnName) { return getCurrentRowAsStruct().getDate(columnName); } + @Override + public T getProtoMessage(int columnIndex, T message) { + return getCurrentRowAsStruct().getProtoMessage(columnIndex, message); + } + + @Override + public T getProtoMessage(String columnName, T message) { + return getCurrentRowAsStruct().getProtoMessage(columnName, message); + } + + @Override + public T getProtoEnum( + int columnIndex, Function method) { + return getCurrentRowAsStruct().getProtoEnum(columnIndex, method); + } + + @Override + public T getProtoEnum( + String columnName, Function method) { + return getCurrentRowAsStruct().getProtoEnum(columnName, method); + } + @Override public Value getValue(int columnIndex) { return getCurrentRowAsStruct().getValue(columnIndex); @@ -440,6 +465,28 @@ public List getDateList(String columnName) { return getCurrentRowAsStruct().getDateList(columnName); } + @Override + public List getProtoMessageList(int columnIndex, T message) { + return getCurrentRowAsStruct().getProtoMessageList(columnIndex, message); + } + + @Override + public List getProtoMessageList(String columnName, T message) { + return getCurrentRowAsStruct().getProtoMessageList(columnName, message); + } + + @Override + public List getProtoEnumList( + int columnIndex, Function method) { + return getCurrentRowAsStruct().getProtoEnumList(columnIndex, method); + } + + @Override + public List getProtoEnumList( + String columnName, Function method) { + return getCurrentRowAsStruct().getProtoEnumList(columnName, method); + } + @Override public List getStructList(int columnIndex) { return getCurrentRowAsStruct().getStructList(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java index 48c989d145e..40c30148d0e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java @@ -28,11 +28,14 @@ import com.google.common.primitives.Booleans; import com.google.common.primitives.Doubles; import com.google.common.primitives.Longs; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Function; import javax.annotation.concurrent.Immutable; /** @@ -217,6 +220,17 @@ protected Date getDateInternal(int columnIndex) { return values.get(columnIndex).getDate(); } + @Override + protected T getProtoMessageInternal(int columnIndex, T message) { + return values.get(columnIndex).getProtoMessage(message); + } + + @Override + protected T getProtoEnumInternal( + int columnIndex, Function method) { + return values.get(columnIndex).getProtoEnum(method); + } + @Override protected Value getValueInternal(int columnIndex) { return values.get(columnIndex); @@ -287,6 +301,18 @@ protected List getTimestampListInternal(int columnIndex) { return values.get(columnIndex).getTimestampArray(); } + @Override + protected List getProtoMessageListInternal( + int columnIndex, T message) { + return values.get(columnIndex).getProtoMessageArray(message); + } + + @Override + protected List getProtoEnumListInternal( + int columnIndex, Function method) { + return values.get(columnIndex).getProtoEnumArray(method); + } + @Override protected List getDateListInternal(int columnIndex) { return values.get(columnIndex).getDateArray(); @@ -354,6 +380,7 @@ private Object getAsObject(int columnIndex) { case BOOL: return getBooleanInternal(columnIndex); case INT64: + case ENUM: return getLongInternal(columnIndex); case FLOAT64: return getDoubleInternal(columnIndex); @@ -368,6 +395,7 @@ private Object getAsObject(int columnIndex) { case PG_JSONB: return getPgJsonbInternal(columnIndex); case BYTES: + case PROTO: return getBytesInternal(columnIndex); case TIMESTAMP: return getTimestampInternal(columnIndex); @@ -380,6 +408,7 @@ private Object getAsObject(int columnIndex) { case BOOL: return getBooleanListInternal(columnIndex); case INT64: + case ENUM: return getLongListInternal(columnIndex); case FLOAT64: return getDoubleListInternal(columnIndex); @@ -394,6 +423,7 @@ private Object getAsObject(int columnIndex) { case PG_JSONB: return getPgJsonbListInternal(columnIndex); case BYTES: + case PROTO: return getBytesListInternal(columnIndex); case TIMESTAMP: return getTimestampListInternal(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java index b767bd6d82c..7108e49ebc2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java @@ -19,8 +19,11 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.math.BigDecimal; import java.util.List; +import java.util.function.Function; /** * A base interface for reading the fields of a {@code STRUCT}. The Cloud Spanner yields {@code @@ -51,6 +54,7 @@ * Struct} is an immutable implementation of {@code StructReader}. */ public interface StructReader { + /** * Returns the type of the underlying data. This will always be a {@code STRUCT} type, with fields * corresponding to the data's columns. For the result of a read or query, this will always match @@ -134,6 +138,60 @@ default String getPgJsonb(String columnName) { throw new UnsupportedOperationException("method should be overwritten"); } + /** + * To get the proto message of generic type {@code T} from Struct. + * + * @param columnIndex Index of the column. + * @param message Proto message object. Message can't be null as it's internally used to find the + * type of proto. Use @code{MyProtoClass.getDefaultInstance()}. @see getDefaultInstance() + * @return The value of a non-{@code NULL} column with type {@link Type#proto(String)} ()}. + */ + default T getProtoMessage(int columnIndex, T message) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto message of type {@code T} from Struct. + * + * @param columnName Name of the column. + * @param message Proto message object. Message can't be null as it's internally used to find the + * type of proto. Use @code{MyProtoClass.getDefaultInstance()}. @see getDefaultInstance() + * @return The value of a non-{@code NULL} column with type {@link Type#proto(String)} ()}. + */ + default T getProtoMessage(String columnName, T message) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto enum of type {@code T} from Struct. + * + * @param columnIndex Index of the column. + * @param method A function that takes enum integer constant as argument and returns the enum. Use + * method @code{forNumber} from generated enum class (eg: MyProtoEnum::forNumber). @see forNumber + * @return The value of a non-{@code NULL} column with type {@link Type#protoEnum(String)} ()}. + */ + default T getProtoEnum( + int columnIndex, Function method) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto enum of type {@code T} from Struct. + * + * @param columnName Name of the column. + * @param method A function that takes enum integer constant as argument and returns the enum. Use + * method @code{forNumber} from generated enum class (eg: MyProtoEnum::forNumber). @see forNumber + * @return The value of a non-{@code NULL} column with type {@link Type#protoEnum(String)} ()}. + */ + default T getProtoEnum( + String columnName, Function method) { + 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); @@ -316,6 +374,64 @@ default List getPgJsonbList(String columnName) { throw new UnsupportedOperationException("method should be overwritten"); }; + /** + * To get the proto message of generic type {@code T} from Struct. + * + * @param columnIndex Index of the column. + * @param message Proto message object. Message can't be null as it's internally used to find the + * type of proto. Use @code{MyProtoClass.getDefaultInstance()}. @see getDefaultInstance() + * @return The value of a non-{@code NULL} column with type {@code + * Type.array(Type.proto(String))}. + */ + default List getProtoMessageList(int columnIndex, T message) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto message of type {@code T} from Struct. + * + * @param columnName Name of the column. + * @param message Proto message object. Message can't be null as it's internally used to find the + * type of proto. Use @code{MyProtoClass.getDefaultInstance()}. @see getDefaultInstance() + * @return The value of a non-{@code NULL} column with type {@code + * Type.array(Type.proto(String))}. + */ + default List getProtoMessageList(String columnName, T message) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto enum of type {@code T} from Struct. + * + * @param columnIndex Index of the column. + * @param method A function that takes enum integer constant as argument and returns the enum. Use + * method @code{forNumber} from generated enum class (eg: MyProtoEnum::forNumber). @see forNumber + * @return The value of a non-{@code NULL} column with type {@code + * Type.array(Type.protoEnum(String))}. + */ + default List getProtoEnumList( + int columnIndex, Function method) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** + * To get the proto enum list of type {@code T} from Struct. + * + * @param columnName Name of the column. + * @param method A function that takes enum integer constant as argument and returns the enum. Use + * method @code{forNumber} from generated enum class (eg: MyProtoEnum::forNumber). @see forNumber + * @return The value of a non-{@code NULL} column with type {@code + * Type.array(Type.protoEnum(String))}. + */ + default List getProtoEnumList( + String columnName, Function method) { + throw new UnsupportedOperationException("method should be overwritten"); + } + /** * Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.bytes())}. The * list returned by this method is lazily constructed. Create a copy of it if you intend to access diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java index 7ba6b9a41e4..e81f0c874ea 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java @@ -33,6 +33,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.TreeMap; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -122,6 +123,24 @@ public static Type pgJsonb() { return TYPE_PG_JSONB; } + /** + * To get the descriptor for the {@code PROTO} type. + * + * @param protoTypeFqn Proto full name + */ + public static Type proto(String protoTypeFqn) { + return new Type(Code.PROTO, protoTypeFqn); + } + + /** + * To get the descriptor for the {@code ENUM} type. + * + * @param protoTypeFqn Proto ENUM full name + */ + public static Type protoEnum(String protoTypeFqn) { + return new Type(Code.ENUM, protoTypeFqn); + } + /** Returns the descriptor for the {@code BYTES} type: a variable-length byte string. */ public static Type bytes() { return TYPE_BYTES; @@ -193,6 +212,7 @@ public static Type struct(StructField... fields) { private final Code code; private final Type arrayElementType; private final ImmutableList structFields; + private String protoTypeFqn; /** * Map of field name to field index. Ambiguous names are indexed to {@link #AMBIGUOUS_FIELD}. The @@ -209,6 +229,11 @@ private Type( this.structFields = structFields; } + private Type(Code code, @Nonnull String protoTypeFqn) { + this(code, null, null); + this.protoTypeFqn = protoTypeFqn; + } + /** Enumerates the categories of types. */ public enum Code { BOOL(TypeCode.BOOL), @@ -219,6 +244,8 @@ public enum Code { STRING(TypeCode.STRING), JSON(TypeCode.JSON), PG_JSONB(TypeCode.JSON, TypeAnnotationCode.PG_JSONB), + PROTO(TypeCode.PROTO), + ENUM(TypeCode.ENUM), BYTES(TypeCode.BYTES), TIMESTAMP(TypeCode.TIMESTAMP), DATE(TypeCode.DATE), @@ -340,6 +367,17 @@ public List getStructFields() { return structFields; } + /** + * Returns the full package name for elements of this {@code Proto or @code Enum} type. + * + * @throws IllegalStateException if {@code code() != Code.PROTO or code() != Code.ENUM} + */ + public String getProtoTypeFqn() { + Preconditions.checkState( + (code == Code.PROTO || code == Code.ENUM), "Illegal call for non-Proto type"); + return protoTypeFqn; + } + /** * Returns the index of the field named {@code fieldName} in this {@code STRUCT} type. * @@ -416,7 +454,8 @@ public boolean equals(Object o) { Type that = (Type) o; return code == that.code && Objects.equals(arrayElementType, that.arrayElementType) - && Objects.equals(structFields, that.structFields); + && Objects.equals(structFields, that.structFields) + && Objects.equals(protoTypeFqn, that.protoTypeFqn); } @Override @@ -435,7 +474,10 @@ com.google.spanner.v1.Type toProto() { for (StructField field : structFields) { fields.addFieldsBuilder().setName(field.getName()).setType(field.getType().toProto()); } + } else if (code == Code.PROTO || code == Code.ENUM) { + proto.setProtoTypeFqn(protoTypeFqn); } + return proto.build(); } @@ -464,6 +506,10 @@ static Type fromProto(com.google.spanner.v1.Type proto) { return timestamp(); case DATE: return date(); + case PROTO: + return proto(proto.getProtoTypeFqn()); + case ENUM: + return protoEnum(proto.getProtoTypeFqn()); case ARRAY: checkArgument( proto.hasArrayElementType(), diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java index 82f03e859e1..23ca8f335e2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java @@ -24,8 +24,13 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; import com.google.protobuf.NullValue; +import com.google.protobuf.ProtocolMessageEnum; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; @@ -35,6 +40,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -218,7 +224,87 @@ public static Value pgJsonb(@Nullable String v) { } /** - * Returns a {@code BYTES} value. + * Return a {@code PROTO} value for not null proto messages. + * + * @param v Not null Proto message. + */ + public static Value protoMessage(AbstractMessage v) { + Preconditions.checkNotNull( + v, "Use protoMessage((ByteArray) null, MyProtoClass.getDescriptor()) for null values."); + return protoMessage( + ByteArray.copyFrom(v.toByteArray()), v.getDescriptorForType().getFullName()); + } + + /** + * Return a {@code PROTO} value + * + * @param v Serialized Proto Array, which may be null. + * @param protoTypeFqn Fully qualified name of proto representing the proto definition. Use static + * method from proto class {@code MyProtoClass.getDescriptor().getFullName()} + */ + public static Value protoMessage(@Nullable ByteArray v, String protoTypeFqn) { + return new ProtoMessageImpl(v == null, v, protoTypeFqn); + } + + /** + * Return a {@code PROTO} value + * + * @param v Serialized Proto Array, which may be null. + * @param descriptor Proto Type Descriptor, use static method from proto class {@code + * MyProtoClass.getDescriptor()}. + */ + public static Value protoMessage(@Nullable ByteArray v, Descriptor descriptor) { + Preconditions.checkNotNull(descriptor, "descriptor can't be null."); + return protoMessage(v, descriptor.getFullName()); + } + + /** + * Return a {@code ENUM} value for not null proto messages. + * + * @param v Proto Enum, which may be null. + */ + public static Value protoEnum(ProtocolMessageEnum v) { + Preconditions.checkNotNull( + v, "Use protoEnum((Long) null, MyProtoEnum.getDescriptor()) for null values."); + return protoEnum(v.getNumber(), v.getDescriptorForType().getFullName()); + } + + /** + * Return a {@code ENUM} value. + * + * @param v Enum non-primitive Integer constant. + * @param protoTypeFqn Fully qualified name of proto representing the enum definition. Use static + * method from proto class {@code MyProtoEnum.getDescriptor().getFullName()} + */ + public static Value protoEnum(@Nullable Long v, String protoTypeFqn) { + return new ProtoEnumImpl(v == null, v, protoTypeFqn); + } + + /** + * Return a {@code ENUM} value. + * + * @param v Enum non-primitive Integer constant. + * @param enumDescriptor Enum Type Descriptor. Use static method from proto class {@code * + * MyProtoEnum.getDescriptor()}. + */ + public static Value protoEnum(@Nullable Long v, EnumDescriptor enumDescriptor) { + Preconditions.checkNotNull(enumDescriptor, "descriptor can't be null."); + return protoEnum(v, enumDescriptor.getFullName()); + } + + /** + * Return a {@code ENUM} value. + * + * @param v Enum integer primitive constant. + * @param protoTypeFqn Fully qualified name of proto representing the enum definition. Use static + * method from proto class {@code MyProtoEnum.getDescriptor().getFullName()} + */ + public static Value protoEnum(long v, String protoTypeFqn) { + return new ProtoEnumImpl(false, v, protoTypeFqn); + } + + /** + * e Returns a {@code BYTES} value. Returns a {@code BYTES} value. * * @param v the value, which may be null */ @@ -421,6 +507,85 @@ public static Value pgJsonbArray(@Nullable Iterable v) { return new PgJsonbArrayImpl(v == null, v == null ? null : immutableCopyOf(v)); } + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + * @param descriptor Proto Type Descriptor, use static method from proto class {@code + * MyProtoClass.getDescriptor()}. + */ + public static Value protoMessageArray( + @Nullable Iterable v, Descriptor descriptor) { + if (v == null) { + return new ProtoMessageArrayImpl(true, null, descriptor.getFullName()); + } + + List serializedArray = new ArrayList<>(); + v.forEach( + (message) -> { + if (message != null) { + serializedArray.add(ByteArray.copyFrom(message.toByteArray())); + } else { + serializedArray.add(null); + } + }); + + return new ProtoMessageArrayImpl(false, serializedArray, descriptor.getFullName()); + } + + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + * @param protoTypeFqn Fully qualified name of proto representing the proto definition. Use static + * method from proto class {@code MyProtoClass.getDescriptor().getFullName()} + */ + public static Value protoMessageArray(@Nullable Iterable v, String protoTypeFqn) { + return new ProtoMessageArrayImpl( + v == null, v != null ? immutableCopyOf(v) : null, protoTypeFqn); + } + + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + * @param descriptor Proto Type Descriptor, use static method from proto class {@code + * MyProtoClass.getDescriptor()}. + */ + public static Value protoEnumArray( + @Nullable Iterable v, EnumDescriptor descriptor) { + if (v == null) { + return new ProtoEnumArrayImpl(true, null, descriptor.getFullName()); + } + + List enumConstValues = new ArrayList<>(); + v.forEach( + (protoEnum) -> { + if (protoEnum != null) { + enumConstValues.add((long) protoEnum.getNumber()); + } else { + enumConstValues.add(null); + } + }); + + return new ProtoEnumArrayImpl(false, enumConstValues, descriptor.getFullName()); + } + + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + * @param protoTypeFqn Fully qualified name of proto representing the enum definition. Use static + * method from proto class {@code MyProtoEnum.getDescriptor().getFullName()} + */ + public static Value protoEnumArray(@Nullable Iterable v, String protoTypeFqn) { + return new ProtoEnumArrayImpl(v == null, v != null ? immutableCopyOf(v) : null, protoTypeFqn); + } + /** * Returns an {@code ARRAY} value. * @@ -541,6 +706,25 @@ public String getPgJsonb() { throw new UnsupportedOperationException("Not implemented"); } + /** + * Returns the value of a {@code PROTO}-typed instance. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public T getProtoMessage(T m) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Returns the value of a {@code ENUM}-typed instance. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public T getProtoEnum( + Function method) { + throw new UnsupportedOperationException("Not implemented"); + } + /** * Returns the value of a {@code BYTES}-typed instance. * @@ -633,6 +817,27 @@ public List getPgJsonbArray() { throw new UnsupportedOperationException("Not implemented"); } + /** + * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself + * will never be {@code null}, elements of that list may be null. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public List getProtoMessageArray(T m) { + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself will + * never be {@code null}, elements of that list may be null. + * + * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type + */ + public List getProtoEnumArray( + Function method) { + throw new UnsupportedOperationException("Not implemented"); + } + /** * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself * will never be {@code null}, elements of that list may be null. @@ -1123,6 +1328,15 @@ public long getInt64() { return value; } + @Override + public T getProtoEnum( + Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + checkNotNull(); + return (T) method.apply((int) value); + } + @Override com.google.protobuf.Value valueToProto() { return com.google.protobuf.Value.newBuilder().setStringValue(Long.toString(value)).build(); @@ -1310,6 +1524,19 @@ public ByteArray getBytes() { return value; } + @Override + public T getProtoMessage(T m) { + Preconditions.checkNotNull( + m, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + checkNotNull(); + try { + return (T) m.toBuilder().mergeFrom(value.toByteArray()).build(); + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + @Override com.google.protobuf.Value valueToProto() { return com.google.protobuf.Value.newBuilder().setStringValue(value.toBase64()).build(); @@ -1321,6 +1548,75 @@ void valueToString(StringBuilder b) { } } + private static class ProtoMessageImpl extends AbstractObjectValue { + + private ProtoMessageImpl(boolean isNull, ByteArray serializedProtoArray, String protoTypeFqn) { + super(isNull, Type.proto(protoTypeFqn), serializedProtoArray); + } + + @Override + public ByteArray getBytes() { + checkNotNull(); + return value; + } + + @Override + public T getProtoMessage(T m) { + Preconditions.checkNotNull( + m, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + checkNotNull(); + try { + return (T) m.toBuilder().mergeFrom(value.toByteArray()).build(); + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + + @Override + com.google.protobuf.Value valueToProto() { + String base64EncodedString = value.toBase64(); + return com.google.protobuf.Value.newBuilder().setStringValue(base64EncodedString).build(); + } + + @Override + void valueToString(StringBuilder b) { + b.append(value.toString()); + } + } + + private static class ProtoEnumImpl extends AbstractObjectValue { + + private ProtoEnumImpl(boolean isNull, Long enumValue, String protoTypeFqn) { + super(isNull, Type.protoEnum(protoTypeFqn), enumValue); + } + + @Override + public long getInt64() { + checkNotNull(); + return value; + } + + @Override + public T getProtoEnum( + Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + checkNotNull(); + return (T) method.apply(value.intValue()); + } + + @Override + void valueToString(StringBuilder b) { + b.append(value.toString()); + } + + @Override + com.google.protobuf.Value valueToProto() { + return com.google.protobuf.Value.newBuilder().setStringValue(Long.toString(value)).build(); + } + } + private static class TimestampImpl extends AbstractObjectValue { private static final String COMMIT_TIMESTAMP_STRING = "spanner.commit_timestamp()"; @@ -1573,6 +1869,24 @@ public List getInt64Array() { return getArray(); } + @Override + public List getProtoEnumArray( + Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + checkNotNull(); + + List protoEnumList = new ArrayList<>(); + for (Long enumIntValue : values) { + if (enumIntValue == null) { + protoEnumList.add(null); + } else { + protoEnumList.add((T) method.apply(enumIntValue.intValue())); + } + } + return protoEnumList; + } + @Override boolean valueEquals(Value v) { Int64ArrayImpl that = (Int64ArrayImpl) v; @@ -1760,6 +2074,28 @@ public List getBytesArray() { return value; } + @Override + public List getProtoMessageArray(T m) { + Preconditions.checkNotNull( + m, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + checkNotNull(); + try { + List protoMessagesList = new ArrayList<>(value.size()); + for (ByteArray protoMessageBytes : value) { + if (protoMessageBytes == null) { + protoMessagesList.add(null); + } else { + protoMessagesList.add( + (T) m.toBuilder().mergeFrom(protoMessageBytes.toByteArray()).build()); + } + } + return protoMessagesList; + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + @Override String elementToString(ByteArray element) { return element.toBase64(); @@ -1789,6 +2125,91 @@ void appendElement(StringBuilder b, Timestamp element) { } } + private static class ProtoMessageArrayImpl extends AbstractArrayValue { + + private ProtoMessageArrayImpl( + boolean isNull, @Nullable List values, String protoTypeFqn) { + super(isNull, Type.proto(protoTypeFqn), values); + } + + @Override + public List getBytesArray() { + return value; + } + + @Override + public List getProtoMessageArray(T m) { + Preconditions.checkNotNull( + m, + "Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value."); + checkNotNull(); + try { + List protoMessagesList = new ArrayList<>(value.size()); + for (ByteArray protoMessageBytes : value) { + if (protoMessageBytes == null) { + protoMessagesList.add(null); + } else { + protoMessagesList.add( + (T) m.toBuilder().mergeFrom(protoMessageBytes.toByteArray()).build()); + } + } + return protoMessagesList; + } catch (InvalidProtocolBufferException e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + } + + @Override + String elementToString(ByteArray element) { + return element.toBase64(); + } + + @Override + void appendElement(StringBuilder b, ByteArray element) { + b.append(element.toString()); + } + } + + private static class ProtoEnumArrayImpl extends AbstractArrayValue { + + private ProtoEnumArrayImpl(boolean isNull, @Nullable List values, String protoTypeFqn) { + super(isNull, Type.protoEnum(protoTypeFqn), values); + } + + @Override + public List getInt64Array() { + return value; + } + + @Override + public List getProtoEnumArray( + Function method) { + Preconditions.checkNotNull( + method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value."); + checkNotNull(); + + List protoEnumList = new ArrayList<>(); + for (Long enumIntValue : value) { + if (enumIntValue == null) { + protoEnumList.add(null); + } else { + protoEnumList.add((T) method.apply(enumIntValue.intValue())); + } + } + return protoEnumList; + } + + @Override + String elementToString(Long element) { + return Long.toString(element); + } + + @Override + void appendElement(StringBuilder b, Long element) { + b.append(element); + } + } + private static class DateArrayImpl extends AbstractArrayValue { private DateArrayImpl(boolean isNull, @Nullable List values) { @@ -1946,6 +2367,10 @@ private Value getValue(int fieldIndex) { return Value.date(value.getDate(fieldIndex)); case TIMESTAMP: return Value.timestamp(value.getTimestamp(fieldIndex)); + case PROTO: + return Value.protoMessage(value.getBytes(fieldIndex), fieldType.getProtoTypeFqn()); + case ENUM: + return Value.protoEnum(value.getLong(fieldIndex), fieldType.getProtoTypeFqn()); case STRUCT: return Value.struct(value.getStruct(fieldIndex)); case ARRAY: @@ -1955,6 +2380,7 @@ private Value getValue(int fieldIndex) { case BOOL: return Value.boolArray(value.getBooleanList(fieldIndex)); case INT64: + case ENUM: return Value.int64Array(value.getLongList(fieldIndex)); case STRING: return Value.stringArray(value.getStringList(fieldIndex)); @@ -1963,6 +2389,7 @@ private Value getValue(int fieldIndex) { case PG_JSONB: return Value.pgJsonbArray(value.getPgJsonbList(fieldIndex)); case BYTES: + case PROTO: return Value.bytesArray(value.getBytesList(fieldIndex)); case FLOAT64: return Value.float64Array(value.getDoubleList(fieldIndex)); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java index ec9e5a43d8f..cdb1fd10b51 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java @@ -19,6 +19,10 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.ProtocolMessageEnum; import java.math.BigDecimal; import javax.annotation.Nullable; @@ -95,6 +99,41 @@ public R to(@Nullable String value) { return handle(Value.string(value)); } + /** Binds to {@code Value.protoMessage(value)} */ + public R to(AbstractMessage m) { + return handle(Value.protoMessage(m)); + } + + /** Binds to {@code Value.protoMessage(value, protoType)} */ + public R to(@Nullable ByteArray v, String protoTypFqn) { + return handle(Value.protoMessage(v, protoTypFqn)); + } + + /** Binds to {@code Value.protoMessage(value, descriptor)} */ + public R to(@Nullable ByteArray v, Descriptor descriptor) { + return handle(Value.protoMessage(v, descriptor)); + } + + /** Binds to {@code Value.protoEnum(value)} */ + public R to(ProtocolMessageEnum value) { + return handle(Value.protoEnum(value)); + } + + /** Binds to {@code Value.protoEnum(value, protoType)} */ + public R to(@Nullable Long v, String protoTypFqn) { + return handle(Value.protoEnum(v, protoTypFqn)); + } + + /** Binds to {@code Value.protoEnum(value, enumDescriptor)} */ + public R to(@Nullable Long v, EnumDescriptor enumDescriptor) { + return handle(Value.protoEnum(v, enumDescriptor)); + } + + /** Binds to {@code Value.protoEnum(value, protoType)} */ + public R to(long v, String protoTypFqn) { + return handle(Value.protoEnum(v, protoTypFqn)); + } + /** Binds to {@code Value.bytes(value)} */ public R to(@Nullable ByteArray value) { return handle(Value.bytes(value)); @@ -203,6 +242,27 @@ public R toTimestampArray(@Nullable Iterable values) { return handle(Value.timestampArray(values)); } + /** Binds to {@code Value.protoMessageArray(values, descriptor)} */ + public R toProtoMessageArray(@Nullable Iterable values, Descriptor descriptor) { + return handle(Value.protoMessageArray(values, descriptor)); + } + + /** Binds to {@code Value.protoMessageArray(values, protoTypeFq)} */ + public R toProtoMessageArray(@Nullable Iterable values, String protoTypeFq) { + return handle(Value.protoMessageArray(values, protoTypeFq)); + } + + /** Binds to {@code Value.protoEnumArray(values, descriptor)} */ + public R toProtoEnumArray( + @Nullable Iterable values, EnumDescriptor descriptor) { + return handle(Value.protoEnumArray(values, descriptor)); + } + + /** Binds to {@code Value.protoEnumArray(values, protoTypeFq)} */ + public R toProtoEnumArray(@Nullable Iterable values, String protoTypeFq) { + return handle(Value.protoEnumArray(values, protoTypeFq)); + } + /** Binds to {@code Value.dateArray(values)} */ public R toDateArray(@Nullable Iterable values) { return handle(Value.dateArray(values)); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java index bb2f2fb817a..24389546669 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java @@ -225,6 +225,7 @@ public void funnel(Struct row, PrimitiveSink into) { funnelValue(type, row.getBoolean(i), into); break; case BYTES: + case PROTO: funnelValue(type, row.getBytes(i), into); break; case DATE: @@ -240,6 +241,7 @@ public void funnel(Struct row, PrimitiveSink into) { funnelValue(type, row.getString(i), into); break; case INT64: + case ENUM: funnelValue(type, row.getLong(i), into); break; case STRING: @@ -274,6 +276,7 @@ private void funnelArray( } break; case BYTES: + case PROTO: into.putInt(row.getBytesList(columnIndex).size()); for (ByteArray value : row.getBytesList(columnIndex)) { funnelValue(Code.BYTES, value, into); @@ -304,6 +307,7 @@ private void funnelArray( } break; case INT64: + case ENUM: into.putInt(row.getLongList(columnIndex).size()); for (Long value : row.getLongList(columnIndex)) { funnelValue(Code.INT64, value, into); @@ -357,6 +361,7 @@ private void funnelValue(Code type, T value, PrimitiveSink into) { into.putBoolean((Boolean) value); break; case BYTES: + case PROTO: ByteArray byteArray = (ByteArray) value; into.putInt(byteArray.length()); into.putBytes(byteArray.toByteArray()); @@ -374,6 +379,7 @@ private void funnelValue(Code type, T value, PrimitiveSink into) { into.putUnencodedChars(stringRepresentation); break; case INT64: + case ENUM: into.putLong((Long) value); break; case PG_NUMERIC: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java index 8690e154f4e..dff915e2cce 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java @@ -25,10 +25,13 @@ import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Value; import com.google.common.base.Preconditions; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import com.google.spanner.v1.ResultSetMetadata; import com.google.spanner.v1.ResultSetStats; import java.math.BigDecimal; import java.util.List; +import java.util.function.Function; /** * {@link ResultSet} implementation used by the Spanner connection API to ensure that the query for @@ -425,6 +428,32 @@ public List getDateList(String columnName) { return delegate.getDateList(columnName); } + @Override + public List getProtoMessageList(int columnIndex, T message) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoMessageList(columnIndex, message); + } + + @Override + public List getProtoMessageList(String columnName, T message) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoMessageList(columnName, message); + } + + @Override + public List getProtoEnumList( + int columnIndex, Function method) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoEnumList(columnIndex, method); + } + + @Override + public List getProtoEnumList( + String columnName, Function method) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoEnumList(columnName, method); + } + @Override public List getStructList(int columnIndex) { Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); @@ -437,6 +466,32 @@ public List getStructList(String columnName) { return delegate.getStructList(columnName); } + @Override + public T getProtoEnum( + int columnIndex, Function method) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoEnum(columnIndex, method); + } + + @Override + public T getProtoEnum( + String columnName, Function method) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoEnum(columnName, method); + } + + @Override + public T getProtoMessage(int columnIndex, T message) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoMessage(columnIndex, message); + } + + @Override + public T getProtoMessage(String columnName, T message) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getProtoMessage(columnName, message); + } + @Override public boolean equals(Object o) { if (!(o instanceof DirectExecuteResultSet)) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java index 07e755b2b25..7370551a46f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java @@ -27,10 +27,13 @@ import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Value; import com.google.common.base.Preconditions; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import com.google.spanner.v1.ResultSetMetadata; import com.google.spanner.v1.ResultSetStats; import java.math.BigDecimal; import java.util.List; +import java.util.function.Function; /** * Forwarding implementation of {@link ResultSet} that forwards all calls to a delegate that can be @@ -430,6 +433,32 @@ public List getDateList(String columnName) { return delegate.getDateList(columnName); } + @Override + public List getProtoMessageList(int columnIndex, T message) { + checkClosed(); + return delegate.getProtoMessageList(columnIndex, message); + } + + @Override + public List getProtoMessageList(String columnName, T message) { + checkClosed(); + return delegate.getProtoMessageList(columnName, message); + } + + @Override + public List getProtoEnumList( + int columnIndex, Function method) { + checkClosed(); + return delegate.getProtoEnumList(columnIndex, method); + } + + @Override + public List getProtoEnumList( + String columnName, Function method) { + checkClosed(); + return delegate.getProtoEnumList(columnName, method); + } + @Override public List getStructList(int columnIndex) { checkClosed(); @@ -441,4 +470,30 @@ public List getStructList(String columnName) { checkClosed(); return delegate.getStructList(columnName); } + + @Override + public T getProtoMessage(int columnIndex, T message) { + checkClosed(); + return delegate.getProtoMessage(columnIndex, message); + } + + @Override + public T getProtoMessage(String columnName, T message) { + checkClosed(); + return delegate.getProtoMessage(columnName, message); + } + + @Override + public T getProtoEnum( + int columnIndex, Function method) { + checkClosed(); + return delegate.getProtoEnum(columnIndex, method); + } + + @Override + public T getProtoEnum( + String columnName, Function method) { + checkClosed(); + return delegate.getProtoEnum(columnName, method); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java index 1b6280a6369..4fc3c67ceba 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java @@ -27,6 +27,8 @@ import com.google.cloud.Date; import com.google.cloud.Timestamp; import com.google.common.base.Throwables; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; @@ -34,6 +36,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Function; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; @@ -95,6 +98,17 @@ protected Date getDateInternal(int columnIndex) { return null; } + @Override + protected T getProtoMessageInternal(int columnIndex, T message) { + return null; + } + + @Override + protected T getProtoEnumInternal( + int columnIndex, Function method) { + return null; + } + @Override protected Value getValueInternal(int columnIndex) { return null; @@ -160,6 +174,18 @@ protected List getTimestampListInternal(int columnIndex) { return null; } + @Override + protected List getProtoMessageListInternal( + int columnIndex, T message) { + return null; + } + + @Override + protected List getProtoEnumListInternal( + int columnIndex, Function method) { + return null; + } + @Override protected List getDateListInternal(int columnIndex) { return null; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java index ff4e92a5215..dba2d2d1136 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java @@ -25,6 +25,8 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.common.base.Function; import com.google.common.base.Strings; @@ -735,6 +737,68 @@ public void getPgJsonb() { assertEquals("[]", resultSet.getPgJsonb(0)); } + @Test + public void getProtoMessage() { + SingerInfo singerInfo1 = + SingerInfo.newBuilder() + .setSingerId(111) + .setNationality("COUNTRY1") + .setGenre(Genre.FOLK) + .build(); + SingerInfo singerInfo2 = SingerInfo.newBuilder().setSingerId(222).setGenre(Genre.JAZZ).build(); + String singerInfoFullName = SingerInfo.getDescriptor().getFullName(); + + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata(Type.struct(Type.StructField.of("f", Type.proto(singerInfoFullName))))) + .addValues(Value.protoMessage(singerInfo1).toProto()) + .addValues( + Value.protoMessage( + ByteArray.copyFrom(singerInfo2.toByteArray()), singerInfoFullName) + .toProto()) + .addValues(Value.protoMessage(null, SingerInfo.getDescriptor().getFullName()).toProto()) + .build()); + consumer.onCompleted(); + + assertTrue(resultSet.next()); + assertEquals(singerInfo1, resultSet.getProtoMessage(0, SingerInfo.getDefaultInstance())); + assertTrue(resultSet.next()); + assertEquals(singerInfo2, resultSet.getProtoMessage(0, SingerInfo.getDefaultInstance())); + assertTrue(resultSet.next()); + assertThrows( + NullPointerException.class, + () -> { + resultSet.getProtoMessage(0, SingerInfo.getDefaultInstance()); + }); + } + + @Test + public void getProtoEnum() { + String genreFullyQualifiedName = Genre.getDescriptor().getFullName(); + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata( + Type.struct(Type.StructField.of("f", Type.protoEnum(genreFullyQualifiedName))))) + .addValues(Value.protoEnum(Genre.FOLK).toProto()) + .addValues(Value.protoEnum(Genre.JAZZ.getNumber(), genreFullyQualifiedName).toProto()) + .addValues(Value.protoEnum(null, genreFullyQualifiedName).toProto()) + .build()); + consumer.onCompleted(); + + assertTrue(resultSet.next()); + assertEquals(Genre.FOLK, resultSet.getProtoEnum(0, Genre::forNumber)); + assertTrue(resultSet.next()); + assertEquals(Genre.JAZZ, resultSet.getProtoEnum(0, Genre::forNumber)); + assertTrue(resultSet.next()); + assertThrows( + NullPointerException.class, + () -> { + resultSet.getProtoEnum(0, Genre::forNumber); + }); + } + @Test public void getBooleanArray() { boolean[] boolArray = {true, true, false}; @@ -876,4 +940,75 @@ public void getPgJsonbList() { assertTrue(resultSet.next()); assertEquals(jsonList, resultSet.getPgJsonbList(0)); } + + @Test + public void getProtoMessageList() { + SingerInfo singerInfo1 = + SingerInfo.newBuilder() + .setSingerId(111) + .setNationality("COUNTRY1") + .setGenre(Genre.FOLK) + .build(); + SingerInfo singerInfo2 = SingerInfo.newBuilder().setSingerId(222).setGenre(Genre.JAZZ).build(); + String singerInfoFullName = SingerInfo.getDescriptor().getFullName(); + + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata( + Type.struct( + Type.StructField.of("f", Type.array(Type.proto(singerInfoFullName)))))) + .addValues( + Value.protoMessageArray( + Arrays.asList(singerInfo1, singerInfo2), SingerInfo.getDescriptor()) + .toProto()) + .addValues( + Value.protoMessageArray( + Arrays.asList(singerInfo2, null, singerInfo1), SingerInfo.getDescriptor()) + .toProto()) + .addValues(Value.protoMessageArray(null, SingerInfo.getDescriptor()).toProto()) + .build()); + consumer.onCompleted(); + + assertTrue(resultSet.next()); + assertEquals( + Arrays.asList(singerInfo1, singerInfo2), + resultSet.getProtoMessageList(0, SingerInfo.getDefaultInstance())); + assertTrue(resultSet.next()); + assertEquals( + Arrays.asList(singerInfo2, null, singerInfo1), + resultSet.getProtoMessageList(0, SingerInfo.getDefaultInstance())); + assertTrue(resultSet.next()); + assertThrows( + NullPointerException.class, + () -> { + resultSet.getProtoMessageList(0, SingerInfo.getDefaultInstance()); + }); + } + + @Test + public void getProtoEnumList() { + String genreFullyQualifiedName = Genre.getDescriptor().getFullName(); + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata( + Type.struct(Type.StructField.of("f", Type.protoEnum(genreFullyQualifiedName))))) + .addValues(Value.protoEnum(Genre.FOLK).toProto()) + .addValues(Value.protoEnum(Genre.JAZZ.getNumber(), genreFullyQualifiedName).toProto()) + .addValues(Value.protoEnum(null, genreFullyQualifiedName).toProto()) + .build()); + consumer.onCompleted(); + + assertTrue(resultSet.next()); + assertEquals(Genre.FOLK, resultSet.getProtoEnum(0, Genre::forNumber)); + assertTrue(resultSet.next()); + assertEquals(Genre.JAZZ, resultSet.getProtoEnum(0, Genre::forNumber)); + assertTrue(resultSet.next()); + assertThrows( + NullPointerException.class, + () -> { + resultSet.getProtoEnum(0, Genre::forNumber); + }); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java index fe2b7aec94b..f38b5e47b8d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java @@ -23,6 +23,8 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.testing.EqualsTester; @@ -545,6 +547,14 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .to(Value.json("{\"key\": \"value\"}}")) .set("jsonNull") .to(Value.json(null)) + .set("protoMessage") + .to(SingerInfo.newBuilder().setSingerId(232).setGenre(Genre.POP).build()) + .set("protoMessageNull") + .to(Value.protoMessage(null, SingerInfo.getDescriptor().getFullName())) + .set("protoEnum") + .to(Genre.JAZZ) + .set("protoEnumNull") + .to(Value.protoEnum(null, SingerInfo.getDescriptor().getFullName())) .set("pgJsonb") .to(Value.pgJsonb("{\"key\": \"value\"}}")) .set("pgJsonbNull") @@ -603,6 +613,24 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .toJsonArray(null) .set("jsonArrValue") .to(Value.jsonArray(ImmutableList.of("{\"key\": \"value1\"}}", "{\"key\": \"value2\"}"))) + .set("protoMessageArr") + .toProtoMessageArray( + ImmutableList.of(SingerInfo.newBuilder().setSingerId(232).setGenre(Genre.POP).build()), + SingerInfo.getDescriptor()) + .set("protoMessageArrNull") + .toProtoMessageArray(null, SingerInfo.getDescriptor()) + .set("protoMessageArrValue") + .to( + Value.protoMessageArray( + ImmutableList.of( + SingerInfo.newBuilder().setSingerId(232).setGenre(Genre.POP).build()), + SingerInfo.getDescriptor())) + .set("protoEnumArr") + .toProtoEnumArray(ImmutableList.of(Genre.JAZZ), Genre.getDescriptor()) + .set("protoEnumArrNull") + .toProtoEnumArray(null, Genre.getDescriptor()) + .set("protoEnumArrValue") + .to(Value.protoEnumArray(ImmutableList.of(Genre.JAZZ), Genre.getDescriptor())) .set("pgJsonbArr") .toPgJsonbArray(ImmutableList.of("{\"key\": \"value1\"}}", "{\"key\": \"value2\"}")) .set("pgJsonbArrNull") diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java index 87be602808c..8e1f257594b 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java @@ -28,9 +28,13 @@ import com.google.cloud.Date; import com.google.cloud.Timestamp; import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.common.primitives.Doubles; import com.google.common.primitives.Longs; import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; @@ -52,6 +56,13 @@ public void resultSetIteration() { BigDecimal bigDecimalVal = BigDecimal.valueOf(123, 2); String stringVal = "stringVal"; String jsonVal = "{\"color\":\"red\",\"value\":\"#f00\"}"; + SingerInfo protoMessageVal = + SingerInfo.newBuilder() + .setSingerId(111) + .setNationality("COUNTRY1") + .setGenre(Genre.FOLK) + .build(); + ProtocolMessageEnum protoEnumVal = Genre.ROCK; String byteVal = "101"; long usecs = 32343; int year = 2018; @@ -80,6 +91,10 @@ public void resultSetIteration() { }; String[] stringArray = {"abc", "def", "ghi"}; String[] jsonArray = {"{}", "{\"color\":\"red\",\"value\":\"#f00\"}", "[]"}; + AbstractMessage[] protoMessageArray = { + protoMessageVal, SingerInfo.newBuilder().setSingerId(1).build() + }; + ProtocolMessageEnum[] protoEnumArray = {protoEnumVal, Genre.JAZZ}; Type type = Type.struct( @@ -94,6 +109,10 @@ public void resultSetIteration() { Type.StructField.of("byteVal", Type.bytes()), Type.StructField.of("timestamp", Type.timestamp()), Type.StructField.of("date", Type.date()), + Type.StructField.of( + "protoMessage", Type.proto(protoMessageVal.getDescriptorForType().getFullName())), + Type.StructField.of( + "protoEnum", Type.protoEnum(protoEnumVal.getDescriptorForType().getFullName())), Type.StructField.of("boolArray", Type.array(Type.bool())), Type.StructField.of("longArray", Type.array(Type.int64())), Type.StructField.of("doubleArray", Type.array(Type.float64())), @@ -103,7 +122,12 @@ public void resultSetIteration() { Type.StructField.of("dateArray", Type.array(Type.date())), Type.StructField.of("stringArray", Type.array(Type.string())), Type.StructField.of("jsonArray", Type.array(Type.json())), - Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb()))); + Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb())), + Type.StructField.of( + "protoMessageArray", + Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))), + Type.StructField.of( + "protoEnumArray", Type.array(Type.protoEnum(Genre.getDescriptor().getFullName())))); Struct struct1 = Struct.newBuilder() .set("f1") @@ -128,6 +152,10 @@ public void resultSetIteration() { .to(Timestamp.ofTimeMicroseconds(usecs)) .set("date") .to(Date.fromYearMonthDay(year, month, day)) + .set("protoMessage") + .to(protoMessageVal) + .set("protoEnum") + .to(protoEnumVal) .set("boolArray") .to(Value.boolArray(boolArray)) .set("longArray") @@ -148,6 +176,14 @@ public void resultSetIteration() { .to(Value.jsonArray(Arrays.asList(jsonArray))) .set("pgJsonbArray") .to(Value.pgJsonbArray(Arrays.asList(jsonArray))) + .set("protoMessageArray") + .to( + Value.protoMessageArray( + Arrays.asList(protoMessageArray), protoMessageVal.getDescriptorForType())) + .set("protoEnumArray") + .to( + Value.protoEnumArray( + Arrays.asList(protoEnumArray), protoEnumVal.getDescriptorForType())) .build(); Struct struct2 = Struct.newBuilder() @@ -173,6 +209,10 @@ public void resultSetIteration() { .to(Timestamp.ofTimeMicroseconds(usecs)) .set("date") .to(Date.fromYearMonthDay(year, month, day)) + .set("protoMessage") + .to(protoMessageVal) + .set("protoEnum") + .to(protoEnumVal) .set("boolArray") .to(Value.boolArray(boolArray)) .set("longArray") @@ -193,6 +233,14 @@ public void resultSetIteration() { .to(Value.jsonArray(Arrays.asList(jsonArray))) .set("pgJsonbArray") .to(Value.pgJsonbArray(Arrays.asList(jsonArray))) + .set("protoMessageArray") + .to( + Value.protoMessageArray( + Arrays.asList(protoMessageArray), protoMessageVal.getDescriptorForType())) + .set("protoEnumArray") + .to( + Value.protoEnumArray( + Arrays.asList(protoEnumArray), protoEnumVal.getDescriptorForType())) .build(); ResultSet rs = ResultSets.forRows(type, Arrays.asList(struct1, struct2)); @@ -259,6 +307,18 @@ public void resultSetIteration() { .isEqualTo(Value.date(Date.fromYearMonthDay(year, month, day))); assertThat(rs.getDate("date")).isEqualTo(Date.fromYearMonthDay(year, month, day)); assertThat(rs.getValue("date")).isEqualTo(Value.date(Date.fromYearMonthDay(year, month, day))); + + assertEquals(protoMessageVal, rs.getProtoMessage(columnIndex, SingerInfo.getDefaultInstance())); + assertEquals(Value.protoMessage(protoMessageVal), rs.getValue(columnIndex++)); + assertEquals( + protoMessageVal, rs.getProtoMessage("protoMessage", SingerInfo.getDefaultInstance())); + assertEquals(Value.protoMessage(protoMessageVal), rs.getValue("protoMessage")); + + assertEquals(protoEnumVal, rs.getProtoEnum(columnIndex, Genre::forNumber)); + assertEquals(Value.protoEnum(protoEnumVal), rs.getValue(columnIndex++)); + assertEquals(protoEnumVal, rs.getProtoEnum("protoEnum", Genre::forNumber)); + assertEquals(Value.protoEnum(protoEnumVal), rs.getValue("protoEnum")); + assertThat(rs.getBooleanArray(columnIndex)).isEqualTo(boolArray); assertThat(rs.getValue(columnIndex++)).isEqualTo(Value.boolArray(boolArray)); assertThat(rs.getBooleanArray("boolArray")).isEqualTo(boolArray); @@ -305,9 +365,29 @@ public void resultSetIteration() { assertThat(rs.getJsonList(columnIndex++)).isEqualTo(Arrays.asList(jsonArray)); assertThat(rs.getJsonList("jsonArray")).isEqualTo(Arrays.asList(jsonArray)); - assertEquals(Arrays.asList(jsonArray), rs.getPgJsonbList(columnIndex)); + assertEquals(Arrays.asList(jsonArray), rs.getPgJsonbList(columnIndex++)); assertEquals(Arrays.asList(jsonArray), rs.getPgJsonbList("pgJsonbArray")); + assertThat(rs.getProtoMessageList(columnIndex, SingerInfo.getDefaultInstance())) + .isEqualTo(Arrays.asList(protoMessageArray)); + assertThat(rs.getValue(columnIndex++)) + .isEqualTo( + Value.protoMessageArray(Arrays.asList(protoMessageArray), SingerInfo.getDescriptor())); + assertThat(rs.getProtoMessageList("protoMessageArray", SingerInfo.getDefaultInstance())) + .isEqualTo(Arrays.asList(protoMessageArray)); + assertThat(rs.getValue("protoMessageArray")) + .isEqualTo( + Value.protoMessageArray(Arrays.asList(protoMessageArray), SingerInfo.getDescriptor())); + + assertThat(rs.getProtoEnumList(columnIndex, Genre::forNumber)) + .isEqualTo(Arrays.asList(protoEnumArray)); + assertThat(rs.getValue(columnIndex)) + .isEqualTo(Value.protoEnumArray(Arrays.asList(protoEnumArray), Genre.getDescriptor())); + assertThat(rs.getProtoEnumList("protoEnumArray", Genre::forNumber)) + .isEqualTo(Arrays.asList(protoEnumArray)); + assertThat(rs.getValue("protoEnumArray")) + .isEqualTo(Value.protoEnumArray(Arrays.asList(protoEnumArray), Genre.getDescriptor())); + assertThat(rs.next()).isTrue(); assertThat(rs.getCurrentRowAsStruct()).isEqualTo(struct2); assertThat(rs.getString(0)).isEqualTo("y"); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SingerProto.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SingerProto.java new file mode 100644 index 00000000000..56fb82c15d2 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SingerProto.java @@ -0,0 +1,1199 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: src/test/resources/com/google/cloud/spanner/singer.proto +package com.google.cloud.spanner; + +public final class SingerProto { + private SingerProto() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + /** Protobuf enum {@code spanner.examples.music.Genre} */ + public enum Genre implements com.google.protobuf.ProtocolMessageEnum { + /** POP = 0; */ + POP(0), + /** JAZZ = 1; */ + JAZZ(1), + /** FOLK = 2; */ + FOLK(2), + /** ROCK = 3; */ + ROCK(3), + ; + + /** POP = 0; */ + public static final int POP_VALUE = 0; + /** JAZZ = 1; */ + public static final int JAZZ_VALUE = 1; + /** FOLK = 2; */ + public static final int FOLK_VALUE = 2; + /** ROCK = 3; */ + public static final int ROCK_VALUE = 3; + + public final int getNumber() { + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static Genre valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static Genre forNumber(int value) { + switch (value) { + case 0: + return POP; + case 1: + return JAZZ; + case 2: + return FOLK; + case 3: + return ROCK; + default: + return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { + return internalValueMap; + } + + private static final com.google.protobuf.Internal.EnumLiteMap internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Genre findValueByNumber(int number) { + return Genre.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { + return getDescriptor().getValues().get(ordinal()); + } + + public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { + return getDescriptor(); + } + + public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { + return com.google.cloud.spanner.SingerProto.getDescriptor().getEnumTypes().get(0); + } + + private static final Genre[] VALUES = values(); + + public static Genre valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private Genre(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:spanner.examples.music.Genre) + } + + public interface SingerInfoOrBuilder + extends + // @@protoc_insertion_point(interface_extends:spanner.examples.music.SingerInfo) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 singer_id = 1; + * + * @return Whether the singerId field is set. + */ + boolean hasSingerId(); + /** + * optional int64 singer_id = 1; + * + * @return The singerId. + */ + long getSingerId(); + + /** + * optional string birth_date = 2; + * + * @return Whether the birthDate field is set. + */ + boolean hasBirthDate(); + /** + * optional string birth_date = 2; + * + * @return The birthDate. + */ + java.lang.String getBirthDate(); + /** + * optional string birth_date = 2; + * + * @return The bytes for birthDate. + */ + com.google.protobuf.ByteString getBirthDateBytes(); + + /** + * optional string nationality = 3; + * + * @return Whether the nationality field is set. + */ + boolean hasNationality(); + /** + * optional string nationality = 3; + * + * @return The nationality. + */ + java.lang.String getNationality(); + /** + * optional string nationality = 3; + * + * @return The bytes for nationality. + */ + com.google.protobuf.ByteString getNationalityBytes(); + + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return Whether the genre field is set. + */ + boolean hasGenre(); + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return The genre. + */ + com.google.cloud.spanner.SingerProto.Genre getGenre(); + } + /** Protobuf type {@code spanner.examples.music.SingerInfo} */ + public static final class SingerInfo extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:spanner.examples.music.SingerInfo) + SingerInfoOrBuilder { + private static final long serialVersionUID = 0L; + // Use SingerInfo.newBuilder() to construct. + private SingerInfo(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private SingerInfo() { + birthDate_ = ""; + nationality_ = ""; + genre_ = 0; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance(UnusedPrivateParameter unused) { + return new SingerInfo(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet getUnknownFields() { + return this.unknownFields; + } + + private SingerInfo( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + bitField0_ |= 0x00000001; + singerId_ = input.readInt64(); + break; + } + case 18: + { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + birthDate_ = bs; + break; + } + case 26: + { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000004; + nationality_ = bs; + break; + } + case 32: + { + int rawValue = input.readEnum(); + @SuppressWarnings("deprecation") + com.google.cloud.spanner.SingerProto.Genre value = + com.google.cloud.spanner.SingerProto.Genre.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(4, rawValue); + } else { + bitField0_ |= 0x00000008; + genre_ = rawValue; + } + break; + } + default: + { + if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return com.google.cloud.spanner.SingerProto + .internal_static_spanner_examples_music_SingerInfo_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.google.cloud.spanner.SingerProto + .internal_static_spanner_examples_music_SingerInfo_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.google.cloud.spanner.SingerProto.SingerInfo.class, + com.google.cloud.spanner.SingerProto.SingerInfo.Builder.class); + } + + private int bitField0_; + public static final int SINGER_ID_FIELD_NUMBER = 1; + private long singerId_; + /** + * optional int64 singer_id = 1; + * + * @return Whether the singerId field is set. + */ + @java.lang.Override + public boolean hasSingerId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional int64 singer_id = 1; + * + * @return The singerId. + */ + @java.lang.Override + public long getSingerId() { + return singerId_; + } + + public static final int BIRTH_DATE_FIELD_NUMBER = 2; + private volatile java.lang.Object birthDate_; + /** + * optional string birth_date = 2; + * + * @return Whether the birthDate field is set. + */ + @java.lang.Override + public boolean hasBirthDate() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string birth_date = 2; + * + * @return The birthDate. + */ + @java.lang.Override + public java.lang.String getBirthDate() { + java.lang.Object ref = birthDate_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + birthDate_ = s; + } + return s; + } + } + /** + * optional string birth_date = 2; + * + * @return The bytes for birthDate. + */ + @java.lang.Override + public com.google.protobuf.ByteString getBirthDateBytes() { + java.lang.Object ref = birthDate_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + birthDate_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int NATIONALITY_FIELD_NUMBER = 3; + private volatile java.lang.Object nationality_; + /** + * optional string nationality = 3; + * + * @return Whether the nationality field is set. + */ + @java.lang.Override + public boolean hasNationality() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional string nationality = 3; + * + * @return The nationality. + */ + @java.lang.Override + public java.lang.String getNationality() { + java.lang.Object ref = nationality_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + nationality_ = s; + } + return s; + } + } + /** + * optional string nationality = 3; + * + * @return The bytes for nationality. + */ + @java.lang.Override + public com.google.protobuf.ByteString getNationalityBytes() { + java.lang.Object ref = nationality_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + nationality_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int GENRE_FIELD_NUMBER = 4; + private int genre_; + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return Whether the genre field is set. + */ + @java.lang.Override + public boolean hasGenre() { + return ((bitField0_ & 0x00000008) != 0); + } + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return The genre. + */ + @java.lang.Override + public com.google.cloud.spanner.SingerProto.Genre getGenre() { + @SuppressWarnings("deprecation") + com.google.cloud.spanner.SingerProto.Genre result = + com.google.cloud.spanner.SingerProto.Genre.valueOf(genre_); + return result == null ? com.google.cloud.spanner.SingerProto.Genre.POP : result; + } + + private byte memoizedIsInitialized = -1; + + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + output.writeInt64(1, singerId_); + } + if (((bitField0_ & 0x00000002) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, birthDate_); + } + if (((bitField0_ & 0x00000004) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, nationality_); + } + if (((bitField0_ & 0x00000008) != 0)) { + output.writeEnum(4, genre_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, singerId_); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, birthDate_); + } + if (((bitField0_ & 0x00000004) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, nationality_); + } + if (((bitField0_ & 0x00000008) != 0)) { + size += com.google.protobuf.CodedOutputStream.computeEnumSize(4, genre_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.google.cloud.spanner.SingerProto.SingerInfo)) { + return super.equals(obj); + } + com.google.cloud.spanner.SingerProto.SingerInfo other = + (com.google.cloud.spanner.SingerProto.SingerInfo) obj; + + if (hasSingerId() != other.hasSingerId()) return false; + if (hasSingerId()) { + if (getSingerId() != other.getSingerId()) return false; + } + if (hasBirthDate() != other.hasBirthDate()) return false; + if (hasBirthDate()) { + if (!getBirthDate().equals(other.getBirthDate())) return false; + } + if (hasNationality() != other.hasNationality()) return false; + if (hasNationality()) { + if (!getNationality().equals(other.getNationality())) return false; + } + if (hasGenre() != other.hasGenre()) return false; + if (hasGenre()) { + if (genre_ != other.genre_) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasSingerId()) { + hash = (37 * hash) + SINGER_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getSingerId()); + } + if (hasBirthDate()) { + hash = (37 * hash) + BIRTH_DATE_FIELD_NUMBER; + hash = (53 * hash) + getBirthDate().hashCode(); + } + if (hasNationality()) { + hash = (37 * hash) + NATIONALITY_FIELD_NUMBER; + hash = (53 * hash) + getNationality().hashCode(); + } + if (hasGenre()) { + hash = (37 * hash) + GENRE_FIELD_NUMBER; + hash = (53 * hash) + genre_; + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseDelimitedFrom( + java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseDelimitedFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + com.google.protobuf.CodedInputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(com.google.cloud.spanner.SingerProto.SingerInfo prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** Protobuf type {@code spanner.examples.music.SingerInfo} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:spanner.examples.music.SingerInfo) + com.google.cloud.spanner.SingerProto.SingerInfoOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return com.google.cloud.spanner.SingerProto + .internal_static_spanner_examples_music_SingerInfo_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.google.cloud.spanner.SingerProto + .internal_static_spanner_examples_music_SingerInfo_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.google.cloud.spanner.SingerProto.SingerInfo.class, + com.google.cloud.spanner.SingerProto.SingerInfo.Builder.class); + } + + // Construct using com.google.cloud.spanner.SingerProto.SingerInfo.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) {} + } + + @java.lang.Override + public Builder clear() { + super.clear(); + singerId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + birthDate_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + nationality_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + genre_ = 0; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return com.google.cloud.spanner.SingerProto + .internal_static_spanner_examples_music_SingerInfo_descriptor; + } + + @java.lang.Override + public com.google.cloud.spanner.SingerProto.SingerInfo getDefaultInstanceForType() { + return com.google.cloud.spanner.SingerProto.SingerInfo.getDefaultInstance(); + } + + @java.lang.Override + public com.google.cloud.spanner.SingerProto.SingerInfo build() { + com.google.cloud.spanner.SingerProto.SingerInfo result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.google.cloud.spanner.SingerProto.SingerInfo buildPartial() { + com.google.cloud.spanner.SingerProto.SingerInfo result = + new com.google.cloud.spanner.SingerProto.SingerInfo(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.singerId_ = singerId_; + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + to_bitField0_ |= 0x00000002; + } + result.birthDate_ = birthDate_; + if (((from_bitField0_ & 0x00000004) != 0)) { + to_bitField0_ |= 0x00000004; + } + result.nationality_ = nationality_; + if (((from_bitField0_ & 0x00000008) != 0)) { + to_bitField0_ |= 0x00000008; + } + result.genre_ = genre_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.setField(field, value); + } + + @java.lang.Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @java.lang.Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.addRepeatedField(field, value); + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.google.cloud.spanner.SingerProto.SingerInfo) { + return mergeFrom((com.google.cloud.spanner.SingerProto.SingerInfo) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.google.cloud.spanner.SingerProto.SingerInfo other) { + if (other == com.google.cloud.spanner.SingerProto.SingerInfo.getDefaultInstance()) + return this; + if (other.hasSingerId()) { + setSingerId(other.getSingerId()); + } + if (other.hasBirthDate()) { + bitField0_ |= 0x00000002; + birthDate_ = other.birthDate_; + onChanged(); + } + if (other.hasNationality()) { + bitField0_ |= 0x00000004; + nationality_ = other.nationality_; + onChanged(); + } + if (other.hasGenre()) { + setGenre(other.getGenre()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.cloud.spanner.SingerProto.SingerInfo parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = + (com.google.cloud.spanner.SingerProto.SingerInfo) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int bitField0_; + + private long singerId_; + /** + * optional int64 singer_id = 1; + * + * @return Whether the singerId field is set. + */ + @java.lang.Override + public boolean hasSingerId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional int64 singer_id = 1; + * + * @return The singerId. + */ + @java.lang.Override + public long getSingerId() { + return singerId_; + } + /** + * optional int64 singer_id = 1; + * + * @param value The singerId to set. + * @return This builder for chaining. + */ + public Builder setSingerId(long value) { + bitField0_ |= 0x00000001; + singerId_ = value; + onChanged(); + return this; + } + /** + * optional int64 singer_id = 1; + * + * @return This builder for chaining. + */ + public Builder clearSingerId() { + bitField0_ = (bitField0_ & ~0x00000001); + singerId_ = 0L; + onChanged(); + return this; + } + + private java.lang.Object birthDate_ = ""; + /** + * optional string birth_date = 2; + * + * @return Whether the birthDate field is set. + */ + public boolean hasBirthDate() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string birth_date = 2; + * + * @return The birthDate. + */ + public java.lang.String getBirthDate() { + java.lang.Object ref = birthDate_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + birthDate_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string birth_date = 2; + * + * @return The bytes for birthDate. + */ + public com.google.protobuf.ByteString getBirthDateBytes() { + java.lang.Object ref = birthDate_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + birthDate_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string birth_date = 2; + * + * @param value The birthDate to set. + * @return This builder for chaining. + */ + public Builder setBirthDate(java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + birthDate_ = value; + onChanged(); + return this; + } + /** + * optional string birth_date = 2; + * + * @return This builder for chaining. + */ + public Builder clearBirthDate() { + bitField0_ = (bitField0_ & ~0x00000002); + birthDate_ = getDefaultInstance().getBirthDate(); + onChanged(); + return this; + } + /** + * optional string birth_date = 2; + * + * @param value The bytes for birthDate to set. + * @return This builder for chaining. + */ + public Builder setBirthDateBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + birthDate_ = value; + onChanged(); + return this; + } + + private java.lang.Object nationality_ = ""; + /** + * optional string nationality = 3; + * + * @return Whether the nationality field is set. + */ + public boolean hasNationality() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional string nationality = 3; + * + * @return The nationality. + */ + public java.lang.String getNationality() { + java.lang.Object ref = nationality_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + nationality_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string nationality = 3; + * + * @return The bytes for nationality. + */ + public com.google.protobuf.ByteString getNationalityBytes() { + java.lang.Object ref = nationality_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + nationality_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string nationality = 3; + * + * @param value The nationality to set. + * @return This builder for chaining. + */ + public Builder setNationality(java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + nationality_ = value; + onChanged(); + return this; + } + /** + * optional string nationality = 3; + * + * @return This builder for chaining. + */ + public Builder clearNationality() { + bitField0_ = (bitField0_ & ~0x00000004); + nationality_ = getDefaultInstance().getNationality(); + onChanged(); + return this; + } + /** + * optional string nationality = 3; + * + * @param value The bytes for nationality to set. + * @return This builder for chaining. + */ + public Builder setNationalityBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + nationality_ = value; + onChanged(); + return this; + } + + private int genre_ = 0; + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return Whether the genre field is set. + */ + @java.lang.Override + public boolean hasGenre() { + return ((bitField0_ & 0x00000008) != 0); + } + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return The genre. + */ + @java.lang.Override + public com.google.cloud.spanner.SingerProto.Genre getGenre() { + @SuppressWarnings("deprecation") + com.google.cloud.spanner.SingerProto.Genre result = + com.google.cloud.spanner.SingerProto.Genre.valueOf(genre_); + return result == null ? com.google.cloud.spanner.SingerProto.Genre.POP : result; + } + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @param value The genre to set. + * @return This builder for chaining. + */ + public Builder setGenre(com.google.cloud.spanner.SingerProto.Genre value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + genre_ = value.getNumber(); + onChanged(); + return this; + } + /** + * optional .spanner.examples.music.Genre genre = 4; + * + * @return This builder for chaining. + */ + public Builder clearGenre() { + bitField0_ = (bitField0_ & ~0x00000008); + genre_ = 0; + onChanged(); + return this; + } + + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:spanner.examples.music.SingerInfo) + } + + // @@protoc_insertion_point(class_scope:spanner.examples.music.SingerInfo) + private static final com.google.cloud.spanner.SingerProto.SingerInfo DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new com.google.cloud.spanner.SingerProto.SingerInfo(); + } + + public static com.google.cloud.spanner.SingerProto.SingerInfo getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated + public static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @java.lang.Override + public SingerInfo parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new SingerInfo(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.google.cloud.spanner.SingerProto.SingerInfo getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_spanner_examples_music_SingerInfo_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_spanner_examples_music_SingerInfo_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + return descriptor; + } + + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + + static { + java.lang.String[] descriptorData = { + "\n\014singer.proto\022\026spanner.examples.music\"v" + + "\n\nSingerInfo\022\021\n\tsinger_id\030\001 \001(\003\022\022\n\nbirth" + + "_date\030\002 \001(\t\022\023\n\013nationality\030\003 \001(\t\022,\n\005genr" + + "e\030\004 \001(\0162\035.spanner.examples.music.Genre*." + + "\n\005Genre\022\007\n\003POP\020\000\022\010\n\004JAZZ\020\001\022\010\n\004FOLK\020\002\022\010\n\004" + + "ROCK\020\003B)\n\030com.google.cloud.spannerB\013Sing" + + "erProtoP\000" + }; + descriptor = + com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( + descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] {}); + internal_static_spanner_examples_music_SingerInfo_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_spanner_examples_music_SingerInfo_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_spanner_examples_music_SingerInfo_descriptor, + new java.lang.String[] { + "SingerId", "BirthDate", "Nationality", "Genre", + }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java index 3ed6fc6c577..9b525f08e70 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java @@ -38,6 +38,7 @@ private abstract static class ScalarTypeTester { private final Type.Code expectedCode; private final TypeCode expectedTypeCode; private final TypeAnnotationCode expectedTypeAnnotationCode; + private String protoTypeFqn = ""; ScalarTypeTester(Type.Code expectedCode, TypeCode expectedTypeCode) { this(expectedCode, expectedTypeCode, TypeAnnotationCode.TYPE_ANNOTATION_CODE_UNSPECIFIED); @@ -52,12 +53,17 @@ private abstract static class ScalarTypeTester { this.expectedTypeAnnotationCode = expectedTypeAnnotationCode; } + ScalarTypeTester(Type.Code expectedCode, TypeCode expectedTypeCode, String protoTypeFqn) { + this(expectedCode, expectedTypeCode); + this.protoTypeFqn = protoTypeFqn; + } + abstract Type newType(); void test() { Type t = newType(); assertThat(t.getCode()).isEqualTo(expectedCode); - assertThat(newType()).isSameInstanceAs(t); // Interned. + assertThat(newType()).isEqualTo(t); // Interned. // String form is deliberately the same as the corresponding type enum in the public API. if (expectedTypeAnnotationCode != TypeAnnotationCode.TYPE_ANNOTATION_CODE_UNSPECIFIED) { assertThat(t.toString()) @@ -70,13 +76,13 @@ void test() { com.google.spanner.v1.Type proto = t.toProto(); assertThat(proto.getCode()).isEqualTo(expectedTypeCode); assertThat(proto.getTypeAnnotation()).isEqualTo(expectedTypeAnnotationCode); + assertThat(proto.getProtoTypeFqn()).isEqualTo(protoTypeFqn); assertThat(proto.hasArrayElementType()).isFalse(); assertThat(proto.hasStructType()).isFalse(); // Round trip. Type fromProto = Type.fromProto(proto); assertThat(fromProto).isEqualTo(t); - assertThat(fromProto).isSameInstanceAs(t); reserializeAndAssert(t); } @@ -172,6 +178,26 @@ Type newType() { }.test(); } + @Test + public void proto() { + new ScalarTypeTester(Type.Code.PROTO, TypeCode.PROTO, "com.google.temp") { + @Override + Type newType() { + return Type.proto("com.google.temp"); + } + }.test(); + } + + @Test + public void protoEnum() { + new ScalarTypeTester(Type.Code.ENUM, TypeCode.ENUM, "com.google.temp.enum") { + @Override + Type newType() { + return Type.protoEnum("com.google.temp.enum"); + } + }.test(); + } + @Test public void timestamp() { new ScalarTypeTester(Type.Code.TIMESTAMP, TypeCode.TIMESTAMP) { @@ -197,6 +223,7 @@ abstract static class ArrayTypeTester { private final TypeCode expectedElementTypeCode; private final TypeAnnotationCode expectedTypeAnnotationCode; private final boolean expectInterned; + private String protoTypeFqn = ""; ArrayTypeTester( Type.Code expectedElementCode, TypeCode expectedElementTypeCode, boolean expectInterned) { @@ -207,6 +234,19 @@ abstract static class ArrayTypeTester { expectInterned); } + ArrayTypeTester( + Type.Code expectedElementCode, + TypeCode expectedElementTypeCode, + String protoTypeFqn, + boolean expectInterned) { + this( + expectedElementCode, + expectedElementTypeCode, + TypeAnnotationCode.TYPE_ANNOTATION_CODE_UNSPECIFIED, + expectInterned); + this.protoTypeFqn = protoTypeFqn; + } + ArrayTypeTester( Type.Code expectedElementCode, TypeCode expectedElementTypeCode, @@ -356,6 +396,26 @@ Type newElementType() { }.test(); } + @Test + public void protoArray() { + new ArrayTypeTester(Type.Code.PROTO, TypeCode.PROTO, "com.google.temp", false) { + @Override + Type newElementType() { + return Type.proto("com.google.temp"); + } + }.test(); + } + + @Test + public void protoEnumArray() { + new ArrayTypeTester(Type.Code.ENUM, TypeCode.ENUM, "com.google.temp.enum", false) { + @Override + Type newElementType() { + return Type.protoEnum("com.google.temp.enum"); + } + }.test(); + } + @Test public void arrayOfArray() { new ArrayTypeTester(Type.Code.ARRAY, TypeCode.ARRAY, false /* not interned */) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java index 91263457baf..8962c0accfc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java @@ -24,6 +24,12 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.ProtocolMessageEnum; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -40,6 +46,8 @@ public class ValueBinderTest { private static final String JSON_METHOD_NAME = "json"; private static final String PG_JSONB_METHOD_NAME = "pgJsonb"; private static final String PG_NUMERIC_METHOD_NAME = "pgNumeric"; + private static final String PROTO_MESSAGE_METHOD_NAME = "protoMessage"; + private static final String PROTO_ENUM_METHOD_NAME = "protoEnum"; public static final String DEFAULT_PG_NUMERIC = "1.23"; private Value lastValue; @@ -121,7 +129,9 @@ public void reflection() } } else if (binderMethod.getParameterTypes().length == 1) { // Test unary null. - if (!binderMethod.getParameterTypes()[0].isPrimitive()) { + if (!binderMethod.getParameterTypes()[0].isPrimitive() + && (!method.getName().equalsIgnoreCase(PROTO_MESSAGE_METHOD_NAME) + && !method.getName().equalsIgnoreCase(PROTO_ENUM_METHOD_NAME))) { if (method.getName().equalsIgnoreCase(JSON_METHOD_NAME)) { // Special case for json to change the method from ValueBinder.to(String) to // ValueBinder.to(Value) @@ -139,7 +149,6 @@ public void reflection() } Value expected = (Value) method.invoke(Value.class, (Object) null); assertThat(lastValue).isEqualTo(expected); - assertThat(binder.to(expected)).isEqualTo(lastReturnValue); assertThat(lastValue).isEqualTo(expected); } @@ -167,6 +176,27 @@ public void reflection() Value expected = (Value) method.invoke(Value.class, defaultObject); assertThat(lastValue).isEqualTo(expected); + assertThat(binder.to(expected)).isEqualTo(lastReturnValue); + assertThat(lastValue).isEqualTo(expected); + } else if (binderMethod.getParameterTypes().length == 2 + && (method.getName().contains(PROTO_MESSAGE_METHOD_NAME) + || method.getName().contains(PROTO_ENUM_METHOD_NAME))) { + // Test unary null. + Object firstArgument = null; + if (binderMethod.getParameterTypes()[0].isPrimitive()) { + firstArgument = 0; + } + + Object secondArgument = "com.proto.example"; + if (binderMethod.getParameterTypes()[1] == Descriptor.class) { + secondArgument = SingerInfo.getDescriptor(); + } else if (binderMethod.getParameterTypes()[1] == EnumDescriptor.class) { + secondArgument = Genre.getDescriptor(); + } + assertThat(binderMethod.invoke(binder, firstArgument, secondArgument)) + .isEqualTo(lastReturnValue); + Value expected = (Value) method.invoke(Value.class, firstArgument, secondArgument); + assertThat(lastValue).isEqualTo(expected); assertThat(binder.to(expected)).isEqualTo(lastReturnValue); assertThat(lastValue).isEqualTo(expected); } else { @@ -234,6 +264,14 @@ public static BigDecimal defaultBigDecimal() { return BigDecimal.valueOf(123, 2); } + public static AbstractMessage defaultAbstractMessage() { + return SingerInfo.newBuilder().setSingerId(323).build(); + } + + public static ProtocolMessageEnum defaultProtocolMessageEnum() { + return Genre.FOLK; + } + public static String defaultString() { return "x"; } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java index a466fab1ab4..7c96d95b514 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java @@ -32,6 +32,8 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.Type.StructField; import com.google.common.base.Strings; import com.google.common.collect.ForwardingList; @@ -629,6 +631,56 @@ public void dateNull() { assertThat(e.getMessage()).contains("null value"); } + @Test + public void protoMessage() { + SingerInfo singerInfo = SingerInfo.newBuilder().setSingerId(111).setGenre(Genre.FOLK).build(); + Value v = Value.protoMessage(singerInfo); + assertThat(v.getType()).isEqualTo(Type.proto(SingerInfo.getDescriptor().getFullName())); + assertThat(v.isNull()).isFalse(); + assertThat(v.getProtoMessage(SingerInfo.getDefaultInstance())).isEqualTo(singerInfo); + assertThat(v.getBytes().toByteArray()).isEqualTo(singerInfo.toByteArray()); + } + + @Test + public void protoMessageNull() { + Value v = Value.protoMessage(null, SingerInfo.getDescriptor().getFullName()); + assertThat(v.getType()).isEqualTo(Type.proto(SingerInfo.getDescriptor().getFullName())); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = + assertThrows( + IllegalStateException.class, + () -> { + v.getProtoMessage(SingerInfo.getDefaultInstance()); + }); + assertThat(e.getMessage()).contains("null value"); + } + + @Test + public void protoEnum() { + Genre genre = Genre.FOLK; + Value v = Value.protoEnum(genre); + assertThat(v.getType()).isEqualTo(Type.protoEnum(Genre.getDescriptor().getFullName())); + assertThat(v.isNull()).isFalse(); + assertThat(v.getInt64()).isEqualTo(genre.getNumber()); + assertEquals(genre, v.getProtoEnum(Genre::forNumber)); + } + + @Test + public void protoEnumNull() { + Value v = Value.protoEnum(null, Genre.getDescriptor().getFullName()); + assertThat(v.getType()).isEqualTo(Type.protoEnum(Genre.getDescriptor().getFullName())); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = + assertThrows( + IllegalStateException.class, + () -> { + v.getProtoEnum(Genre::forNumber); + }); + assertThat(e.getMessage()).contains("null value"); + } + @Test public void boolArray() { Value v = Value.boolArray(new boolean[] {true, false}); @@ -1050,6 +1102,70 @@ public void dateArrayNull() { assertThat(e.getMessage()).contains("null value"); } + @Test + public void protoMessageArray() { + SingerInfo singerInfo1 = SingerInfo.newBuilder().setSingerId(111).setGenre(Genre.FOLK).build(); + SingerInfo singerInfo2 = SingerInfo.newBuilder().setSingerId(222).build(); + Value v = + Value.protoMessageArray( + Arrays.asList(singerInfo1, null, singerInfo2), SingerInfo.getDescriptor()); + assertThat(v.getType()) + .isEqualTo(Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))); + assertThat(v.isNull()).isFalse(); + assertThat(v.getProtoMessageArray(SingerInfo.getDefaultInstance())) + .containsExactly(singerInfo1, null, singerInfo2); + assertThat(v.getBytesArray()) + .containsExactly( + ByteArray.copyFrom(singerInfo1.toByteArray()), + null, + ByteArray.copyFrom(singerInfo2.toByteArray())); + } + + @Test + public void protoMessageNullArray() { + Value v = Value.protoMessageArray(null, SingerInfo.getDescriptor()); + assertThat(v.getType()) + .isEqualTo(Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = + assertThrows( + IllegalStateException.class, + () -> { + v.getProtoMessageArray(SingerInfo.getDefaultInstance()); + }); + assertThat(e.getMessage()).contains("null value"); + } + + @Test + public void protoEnumArray() { + Genre genre1 = Genre.ROCK; + Genre genre2 = Genre.JAZZ; + Value v = Value.protoEnumArray(Arrays.asList(genre1, null, genre2), Genre.getDescriptor()); + assertThat(v.getType()) + .isEqualTo(Type.array(Type.protoEnum(Genre.getDescriptor().getFullName()))); + assertThat(v.isNull()).isFalse(); + assertThat(v.getProtoEnumArray(Genre::forNumber)).containsExactly(genre1, null, genre2); + assertThat(v.getInt64Array()) + .containsExactly((long) genre1.getNumber(), null, (long) genre2.getNumber()); + } + + @Test + public void protoEnumNullArray() { + Value v = Value.protoEnumArray(null, Genre.getDescriptor()); + assertThat(v.getType()) + .isEqualTo(Type.array(Type.protoEnum(Genre.getDescriptor().getFullName()))); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = + assertThrows( + IllegalStateException.class, + () -> { + v.getProtoEnumArray(Genre::forNumber); + }); + assertThat(e.getMessage()).contains("null value"); + } + @Test public void struct() { Struct struct = Struct.newBuilder().set("f1").to("v1").set("f2").to(30).build(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java index 1f3f59e96ab..4e8fb0cfcbb 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java @@ -29,6 +29,8 @@ import com.google.cloud.spanner.AbortedException; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Struct.Builder; @@ -65,6 +67,10 @@ public class ChecksumResultSetTest { .to(Value.json("{\"color\":\"red\",\"value\":\"#ff0\"}")) .set("pgJsonbVal") .to(Value.pgJsonb("{\"color\":\"red\",\"value\":\"#00f\"}")) + .set("protoMessageVal") + .to(SingerInfo.newBuilder().setSingerId(23).build()) + .set("protoEnumVal") + .to(Genre.JAZZ) .set("byteVal") .to(Value.bytes(ByteArray.copyFrom("bytes".getBytes(StandardCharsets.UTF_8)))) .set("timestamp") @@ -104,6 +110,15 @@ public class ChecksumResultSetTest { .to( Value.pgJsonbArray( Arrays.asList("{\"color\":\"red\",\"value\":\"#f00\"}", null, "[]"))) + .set("protoMessageArray") + .to( + Value.protoMessageArray( + Arrays.asList( + SingerInfo.newBuilder().setSingerId(23).build(), + SingerInfo.getDefaultInstance()), + SingerInfo.getDescriptor())) + .set("protoEnumArray") + .to(Value.protoEnumArray(Arrays.asList(Genre.JAZZ, Genre.ROCK), Genre.getDescriptor())) .build(); @Test @@ -118,6 +133,10 @@ public void testRetry() { Type.StructField.of("stringVal", Type.string()), Type.StructField.of("jsonVal", Type.json()), Type.StructField.of("pgJsonbVal", Type.pgJsonb()), + Type.StructField.of( + "protoMessageVal", Type.proto(SingerInfo.getDescriptor().getFullName())), + Type.StructField.of( + "protoEnumVal", Type.protoEnum(Genre.getDescriptor().getFullName())), Type.StructField.of("byteVal", Type.bytes()), Type.StructField.of("timestamp", Type.timestamp()), Type.StructField.of("date", Type.date()), @@ -131,7 +150,12 @@ public void testRetry() { Type.StructField.of("dateArray", Type.array(Type.date())), Type.StructField.of("stringArray", Type.array(Type.string())), Type.StructField.of("jsonArray", Type.array(Type.json())), - Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb()))); + Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb())), + Type.StructField.of( + "protoMessageArray", + Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))), + Type.StructField.of( + "protoEnumArray", Type.array(Type.protoEnum(Genre.getDescriptor().getFullName())))); Struct rowNonNullValues = Struct.newBuilder() .set("boolVal") @@ -150,6 +174,10 @@ public void testRetry() { .to(Value.json("{\"color\":\"red\",\"value\":\"#f00\"}")) .set("pgJsonbVal") .to(Value.pgJsonb("{\"color\":\"red\",\"value\":\"#f00\"}")) + .set("protoMessageVal") + .to(SingerInfo.newBuilder().setSingerId(98).setNationality("C1").build()) + .set("protoEnumVal") + .to(Genre.POP) .set("byteVal") .to(Value.bytes(ByteArray.copyFrom("test".getBytes(StandardCharsets.UTF_8)))) .set("timestamp") @@ -192,6 +220,15 @@ public void testRetry() { .to( Value.pgJsonbArray( Arrays.asList("{\"color\":\"red\",\"value\":\"#f00\"}", null, "{}"))) + .set("protoMessageArray") + .to( + Value.protoMessageArray( + Arrays.asList( + SingerInfo.newBuilder().setSingerId(11).setNationality("C1").build(), + SingerInfo.getDefaultInstance()), + SingerInfo.getDescriptor())) + .set("protoEnumArray") + .to(Value.protoEnumArray(Arrays.asList(Genre.POP, Genre.ROCK), Genre.getDescriptor())) .build(); Struct rowNullValues = Struct.newBuilder() @@ -211,6 +248,10 @@ public void testRetry() { .to(Value.json(null)) .set("pgJsonbVal") .to(Value.pgJsonb(null)) + .set("protoMessageVal") + .to(Value.protoMessage(null, SingerInfo.getDescriptor().getFullName())) + .set("protoEnumVal") + .to(Value.protoEnum(null, Genre.getDescriptor().getFullName())) .set("byteVal") .to((ByteArray) null) .set("timestamp") @@ -239,6 +280,10 @@ public void testRetry() { .toJsonArray(null) .set("pgJsonbArray") .toPgJsonbArray(null) + .set("protoMessageArray") + .to(Value.protoMessageArray(null, SingerInfo.getDescriptor())) + .set("protoEnumArray") + .to(Value.protoEnumArray(null, Genre.getDescriptor())) .build(); ParsedStatement parsedStatement = mock(ParsedStatement.class); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DirectExecuteResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DirectExecuteResultSetTest.java index 346055060ab..1e4f96d1568 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DirectExecuteResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DirectExecuteResultSetTest.java @@ -25,15 +25,20 @@ import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.StructField; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -123,7 +128,7 @@ private void callMethods( boolean exception = false; int numberOfParameters = method.getParameterTypes().length; Class firstParameterType = null; - if (numberOfParameters == 1) { + if (numberOfParameters >= 1) { firstParameterType = method.getParameterTypes()[0]; } try { @@ -140,8 +145,32 @@ private void callMethods( fail("unknown parameter type"); } break; + case 2: + Class secondParameterType = method.getParameterTypes()[1]; + Object firstArgument = null, secondArgument = null; + + if (firstParameterType == String.class) { + firstArgument = "test"; + } else if (firstParameterType == int.class) { + firstArgument = 0; + } + + if (secondParameterType == Function.class) { + Function lambdaFunction = + (val) -> Genre.forNumber(val.intValue()); + secondArgument = lambdaFunction; + } else if (secondParameterType == AbstractMessage.class) { + secondArgument = SingerInfo.getDefaultInstance(); + } + + if (firstArgument != null && secondArgument != null) { + method.invoke(subject, firstArgument, secondArgument); + } else { + fail("unknown parameter type"); + } + break; default: - fail("method with more than 1 parameter is unknown"); + fail("method with more than 2 parameters is unknown"); } } catch (InvocationTargetException e) { if (e.getCause().getClass().equals(expectedException)) { @@ -261,6 +290,25 @@ public void testValidMethodCall() throws IllegalArgumentException { subject.getPgJsonbList("test2"); verify(delegate).getPgJsonbList("test2"); + subject.getProtoMessage(0, SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessage(0, SingerInfo.getDefaultInstance()); + subject.getProtoMessage("test0", SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessage("test0", SingerInfo.getDefaultInstance()); + subject.getProtoMessageList(0, SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessageList(0, SingerInfo.getDefaultInstance()); + subject.getProtoMessageList("test0", SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessageList("test0", SingerInfo.getDefaultInstance()); + + Function lambdaFunction = Genre::forNumber; + subject.getProtoEnum(0, lambdaFunction); + verify(delegate).getProtoEnum(0, lambdaFunction); + subject.getProtoEnum("test0", lambdaFunction); + verify(delegate).getProtoEnum("test0", lambdaFunction); + subject.getProtoEnumList(0, lambdaFunction); + verify(delegate).getProtoEnumList(0, lambdaFunction); + subject.getProtoEnumList("test0", lambdaFunction); + verify(delegate).getProtoEnumList("test0", lambdaFunction); + subject.getStructList(0); subject.getStructList("test0"); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java index c3ac655a40e..e783cff25bf 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java @@ -19,6 +19,8 @@ import com.google.cloud.Date; import com.google.cloud.Timestamp; import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.common.io.BaseEncoding; import com.google.protobuf.ListValue; import com.google.protobuf.NullValue; @@ -32,6 +34,9 @@ import com.google.spanner.v1.TypeAnnotationCode; import com.google.spanner.v1.TypeCode; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Random; /** @@ -40,73 +45,103 @@ */ public class RandomResultSetGenerator { private static Type[] generateTypes(Dialect dialect) { - return new Type[] { - Type.newBuilder().setCode(TypeCode.BOOL).build(), - Type.newBuilder().setCode(TypeCode.INT64).build(), - Type.newBuilder().setCode(TypeCode.FLOAT64).build(), - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.NUMERIC) - .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) - .build() - : Type.newBuilder().setCode(TypeCode.NUMERIC).build(), - Type.newBuilder().setCode(TypeCode.STRING).build(), - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.JSON) - .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) - .build() - : Type.newBuilder().setCode(TypeCode.JSON).build(), - Type.newBuilder().setCode(TypeCode.BYTES).build(), - Type.newBuilder().setCode(TypeCode.DATE).build(), - Type.newBuilder().setCode(TypeCode.TIMESTAMP).build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType( - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.NUMERIC) - .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) - : Type.newBuilder().setCode(TypeCode.NUMERIC)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType( - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.JSON) - .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) - : Type.newBuilder().setCode(TypeCode.JSON)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP)) - .build(), - }; + List types = + new ArrayList( + Arrays.asList( + Type.newBuilder().setCode(TypeCode.BOOL).build(), + Type.newBuilder().setCode(TypeCode.INT64).build(), + Type.newBuilder().setCode(TypeCode.FLOAT64).build(), + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + .build() + : Type.newBuilder().setCode(TypeCode.NUMERIC).build(), + Type.newBuilder().setCode(TypeCode.STRING).build(), + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.JSON) + .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) + .build() + : Type.newBuilder().setCode(TypeCode.JSON).build(), + Type.newBuilder().setCode(TypeCode.BYTES).build(), + Type.newBuilder().setCode(TypeCode.DATE).build(), + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + : Type.newBuilder().setCode(TypeCode.NUMERIC)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.JSON) + .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) + : Type.newBuilder().setCode(TypeCode.JSON)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP)) + .build())); + + appendProtoTypes(types, dialect); + Type[] typeArray = new Type[types.size()]; + typeArray = types.toArray(typeArray); + return typeArray; + } + + /** To append Proto & Enum types * */ + private static void appendProtoTypes(List types, Dialect dialect) { + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { + types.add(Type.newBuilder().setCode(TypeCode.PROTO).setProtoTypeFqn("testProto").build()); + types.add(Type.newBuilder().setCode(TypeCode.ENUM).setProtoTypeFqn("testEnum").build()); + types.add( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + Type.newBuilder() + .setCode(TypeCode.PROTO) + .setProtoTypeFqn(SingerInfo.getDescriptor().getFullName())) + .build()); + types.add( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + Type.newBuilder() + .setCode(TypeCode.ENUM) + .setProtoTypeFqn(Genre.getDescriptor().getFullName())) + .build()); + } } private static ResultSetMetadata generateMetadata(Type[] types) { @@ -171,6 +206,7 @@ private void setRandomValue(Value.Builder builder, Type type) { break; case STRING: case BYTES: + case PROTO: byte[] bytes = new byte[random.nextInt(200)]; random.nextBytes(bytes); builder.setStringValue(BaseEncoding.base64().encode(bytes)); @@ -199,6 +235,7 @@ private void setRandomValue(Value.Builder builder, Type type) { } break; case INT64: + case ENUM: builder.setStringValue(String.valueOf(random.nextLong())); break; case TIMESTAMP: diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java index aae85bcec62..1fc8d1cb790 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java @@ -40,6 +40,8 @@ import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Statement; @@ -52,6 +54,7 @@ import com.google.cloud.spanner.Value; import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement; import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType; +import com.google.protobuf.ProtocolMessageEnum; import com.google.rpc.RetryInfo; import com.google.spanner.v1.ResultSetStats; import io.grpc.Metadata; @@ -499,13 +502,25 @@ public void testChecksumResultSet() { "[{\"color\":\"red\",\"value\":\"#f00\"},{\"color\":\"green\",\"value\":\"#0f0\"},{\"color\":\"blue\",\"value\":\"#00f\"},{\"color\":\"cyan\",\"value\":\"#0ff\"},{\"color\":\"magenta\",\"value\":\"#f0f\"},{\"color\":\"yellow\",\"value\":\"#ff0\"},{\"color\":\"black\",\"value\":\"#000\"}]"; String emptyArrayJson = "[]"; String simpleJson = "{\"color\":\"red\",\"value\":\"#f00\"}"; + SingerInfo protoMessageVal = + SingerInfo.newBuilder() + .setSingerId(111) + .setNationality("COUNTRY1") + .setGenre(Genre.FOLK) + .build(); + ProtocolMessageEnum protoEnumVal = Genre.ROCK; ResultSet delegate1 = ResultSets.forRows( Type.struct( StructField.of("ID", Type.int64()), StructField.of("NAME", Type.string()), StructField.of("AMOUNT", Type.numeric()), - StructField.of("JSON", Type.json())), + StructField.of("JSON", Type.json()), + StructField.of( + "PROTO", Type.proto(protoMessageVal.getDescriptorForType().getFullName())), + StructField.of( + "PROTOENUM", + Type.protoEnum(protoEnumVal.getDescriptorForType().getFullName()))), Arrays.asList( Struct.newBuilder() .set("ID") @@ -516,6 +531,10 @@ public void testChecksumResultSet() { .to(BigDecimal.valueOf(550, 2)) .set("JSON") .to(Value.json(simpleJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(protoEnumVal) .build(), Struct.newBuilder() .set("ID") @@ -526,6 +545,10 @@ public void testChecksumResultSet() { .to(BigDecimal.valueOf(750, 2)) .set("JSON") .to(Value.json(arrayJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(Genre.JAZZ) .build())); ChecksumResultSet rs1 = transaction.createChecksumResultSet(delegate1, parsedStatement, AnalyzeMode.NONE); @@ -535,7 +558,12 @@ public void testChecksumResultSet() { StructField.of("ID", Type.int64()), StructField.of("NAME", Type.string()), StructField.of("AMOUNT", Type.numeric()), - StructField.of("JSON", Type.json())), + StructField.of("JSON", Type.json()), + StructField.of( + "PROTO", Type.proto(protoMessageVal.getDescriptorForType().getFullName())), + StructField.of( + "PROTOENUM", + Type.protoEnum(protoEnumVal.getDescriptorForType().getFullName()))), Arrays.asList( Struct.newBuilder() .set("ID") @@ -546,6 +574,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("5.50")) .set("JSON") .to(Value.json(simpleJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(protoEnumVal) .build(), Struct.newBuilder() .set("ID") @@ -556,6 +588,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("7.50")) .set("JSON") .to(Value.json(arrayJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(Genre.JAZZ) .build())); ChecksumResultSet rs2 = transaction.createChecksumResultSet(delegate2, parsedStatement, AnalyzeMode.NONE); @@ -566,7 +602,12 @@ public void testChecksumResultSet() { StructField.of("ID", Type.int64()), StructField.of("NAME", Type.string()), StructField.of("AMOUNT", Type.numeric()), - StructField.of("JSON", Type.json())), + StructField.of("JSON", Type.json()), + StructField.of( + "PROTO", Type.proto(protoMessageVal.getDescriptorForType().getFullName())), + StructField.of( + "PROTOENUM", + Type.protoEnum(protoEnumVal.getDescriptorForType().getFullName()))), Arrays.asList( Struct.newBuilder() .set("ID") @@ -577,6 +618,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("7.50")) .set("JSON") .to(Value.json(arrayJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(Genre.JAZZ) .build(), Struct.newBuilder() .set("ID") @@ -587,6 +632,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("5.50")) .set("JSON") .to(Value.json(simpleJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(protoEnumVal) .build())); ChecksumResultSet rs3 = transaction.createChecksumResultSet(delegate3, parsedStatement, AnalyzeMode.NONE); @@ -598,7 +647,12 @@ public void testChecksumResultSet() { StructField.of("ID", Type.int64()), StructField.of("NAME", Type.string()), StructField.of("AMOUNT", Type.numeric()), - StructField.of("JSON", Type.json())), + StructField.of("JSON", Type.json()), + StructField.of( + "PROTO", Type.proto(protoMessageVal.getDescriptorForType().getFullName())), + StructField.of( + "PROTOENUM", + Type.protoEnum(protoEnumVal.getDescriptorForType().getFullName()))), Arrays.asList( Struct.newBuilder() .set("ID") @@ -609,6 +663,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("5.50")) .set("JSON") .to(Value.json(simpleJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(protoEnumVal) .build(), Struct.newBuilder() .set("ID") @@ -619,6 +677,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("7.50")) .set("JSON") .to(Value.json(arrayJson)) + .set("PROTO") + .to(protoMessageVal) + .set("PROTOENUM") + .to(Genre.JAZZ) .build(), Struct.newBuilder() .set("ID") @@ -629,6 +691,10 @@ public void testChecksumResultSet() { .to(new BigDecimal("9.99")) .set("JSON") .to(Value.json(emptyArrayJson)) + .set("PROTO") + .to(null, SingerInfo.getDescriptor()) + .set("PROTOENUM") + .to(Genre.POP) .build())); ChecksumResultSet rs4 = transaction.createChecksumResultSet(delegate4, parsedStatement, AnalyzeMode.NONE); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSetTest.java index 3f69c2171e4..bbb34675147 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSetTest.java @@ -26,16 +26,21 @@ import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SingerProto.Genre; +import com.google.cloud.spanner.SingerProto.SingerInfo; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.StructField; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ProtocolMessageEnum; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -164,7 +169,7 @@ private void callMethods( boolean exception = false; int numberOfParameters = method.getParameterTypes().length; Class firstParameterType = null; - if (numberOfParameters == 1) { + if (numberOfParameters >= 1) { firstParameterType = method.getParameterTypes()[0]; } try { @@ -181,8 +186,32 @@ private void callMethods( fail("unknown parameter type"); } break; + case 2: + Class secondParameterType = method.getParameterTypes()[1]; + Object firstArgument = null, secondArgument = null; + + if (firstParameterType == String.class) { + firstArgument = "test"; + } else if (firstParameterType == int.class) { + firstArgument = 0; + } + + if (secondParameterType == Function.class) { + Function lambdaFunction = + (val) -> Genre.forNumber(val.intValue()); + secondArgument = lambdaFunction; + } else if (secondParameterType == AbstractMessage.class) { + secondArgument = SingerInfo.getDefaultInstance(); + } + + if (firstArgument != null && secondArgument != null) { + method.invoke(subject, firstArgument, secondArgument); + } else { + fail("unknown parameter type"); + } + break; default: - fail("method with more than 1 parameter is unknown"); + fail("method with more than 2 parameters is unknown"); } } catch (InvocationTargetException e) { if (e.getCause().getClass().equals(expectedException)) { @@ -296,6 +325,25 @@ public void testValidMethodCall() throws IllegalArgumentException { subject.getJsonList("test2"); verify(delegate).getJsonList("test2"); + subject.getProtoMessage(0, SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessage(0, SingerInfo.getDefaultInstance()); + subject.getProtoMessage("test0", SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessage("test0", SingerInfo.getDefaultInstance()); + subject.getProtoMessageList(0, SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessageList(0, SingerInfo.getDefaultInstance()); + subject.getProtoMessageList("test0", SingerInfo.getDefaultInstance()); + verify(delegate).getProtoMessageList("test0", SingerInfo.getDefaultInstance()); + + Function lambdaFunction = Genre::forNumber; + subject.getProtoEnum(0, lambdaFunction); + verify(delegate).getProtoEnum(0, lambdaFunction); + subject.getProtoEnum("test0", lambdaFunction); + verify(delegate).getProtoEnum("test0", lambdaFunction); + subject.getProtoEnumList(0, lambdaFunction); + verify(delegate).getProtoEnumList(0, lambdaFunction); + subject.getProtoEnumList("test0", lambdaFunction); + verify(delegate).getProtoEnumList("test0", lambdaFunction); + subject.getStructList(0); subject.getStructList("test0"); diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/singer.proto b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/singer.proto new file mode 100644 index 00000000000..b62b48ed67f --- /dev/null +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/singer.proto @@ -0,0 +1,21 @@ +syntax = "proto2"; + +package spanner.examples.music; + +option java_package = "com.google.cloud.spanner"; +option java_outer_classname = "SingerProto"; +option java_multiple_files = false; + +message SingerInfo { + optional int64 singer_id = 1; + optional string birth_date = 2; + optional string nationality = 3; + optional Genre genre = 4; +} + +enum Genre { + POP = 0; + JAZZ = 1; + FOLK = 2; + ROCK = 3; +} diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Type.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Type.java index 7657be926d2..52ab5b34cc9 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Type.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/Type.java @@ -41,6 +41,7 @@ private Type(com.google.protobuf.GeneratedMessageV3.Builder builder) { private Type() { code_ = 0; typeAnnotation_ = 0; + protoTypeFqn_ = ""; } @java.lang.Override @@ -54,6 +55,96 @@ public final com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } + private Type( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + int rawValue = input.readEnum(); + + code_ = rawValue; + break; + } + case 18: + { + com.google.spanner.v1.Type.Builder subBuilder = null; + if (arrayElementType_ != null) { + subBuilder = arrayElementType_.toBuilder(); + } + arrayElementType_ = + input.readMessage(com.google.spanner.v1.Type.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(arrayElementType_); + arrayElementType_ = subBuilder.buildPartial(); + } + + break; + } + case 26: + { + com.google.spanner.v1.StructType.Builder subBuilder = null; + if (structType_ != null) { + subBuilder = structType_.toBuilder(); + } + structType_ = + input.readMessage(com.google.spanner.v1.StructType.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(structType_); + structType_ = subBuilder.buildPartial(); + } + + break; + } + case 32: + { + int rawValue = input.readEnum(); + + typeAnnotation_ = rawValue; + break; + } + case 42: + { + java.lang.String s = input.readStringRequireUtf8(); + + protoTypeFqn_ = s; + break; + } + default: + { + if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return com.google.spanner.v1.TypeProto.internal_static_google_spanner_v1_Type_descriptor; } @@ -249,6 +340,43 @@ public com.google.spanner.v1.TypeAnnotationCode getTypeAnnotation() { return result == null ? com.google.spanner.v1.TypeAnnotationCode.UNRECOGNIZED : result; } + public static final int PROTO_TYPE_FQN_FIELD_NUMBER = 5; + private volatile java.lang.Object protoTypeFqn_; + /** + * string proto_type_fqn = 5; + * + * @return The protoTypeFqn. + */ + @java.lang.Override + public java.lang.String getProtoTypeFqn() { + java.lang.Object ref = protoTypeFqn_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + protoTypeFqn_ = s; + return s; + } + } + /** + * string proto_type_fqn = 5; + * + * @return The bytes for protoTypeFqn. + */ + @java.lang.Override + public com.google.protobuf.ByteString getProtoTypeFqnBytes() { + java.lang.Object ref = protoTypeFqn_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + protoTypeFqn_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + private byte memoizedIsInitialized = -1; @java.lang.Override @@ -276,7 +404,10 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io != com.google.spanner.v1.TypeAnnotationCode.TYPE_ANNOTATION_CODE_UNSPECIFIED.getNumber()) { output.writeEnum(4, typeAnnotation_); } - getUnknownFields().writeTo(output); + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(protoTypeFqn_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, protoTypeFqn_); + } + unknownFields.writeTo(output); } @java.lang.Override @@ -298,7 +429,10 @@ public int getSerializedSize() { != com.google.spanner.v1.TypeAnnotationCode.TYPE_ANNOTATION_CODE_UNSPECIFIED.getNumber()) { size += com.google.protobuf.CodedOutputStream.computeEnumSize(4, typeAnnotation_); } - size += getUnknownFields().getSerializedSize(); + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(protoTypeFqn_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, protoTypeFqn_); + } + size += unknownFields.getSerializedSize(); memoizedSize = size; return size; } @@ -323,7 +457,8 @@ public boolean equals(final java.lang.Object obj) { if (!getStructType().equals(other.getStructType())) return false; } if (typeAnnotation_ != other.typeAnnotation_) return false; - if (!getUnknownFields().equals(other.getUnknownFields())) return false; + if (!getProtoTypeFqn().equals(other.getProtoTypeFqn())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; return true; } @@ -346,7 +481,9 @@ public int hashCode() { } hash = (37 * hash) + TYPE_ANNOTATION_FIELD_NUMBER; hash = (53 * hash) + typeAnnotation_; - hash = (29 * hash) + getUnknownFields().hashCode(); + hash = (37 * hash) + PROTO_TYPE_FQN_FIELD_NUMBER; + hash = (53 * hash) + getProtoTypeFqn().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; } @@ -473,10 +610,17 @@ public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { } // Construct using com.google.spanner.v1.Type.newBuilder() - private Builder() {} + private Builder() { + maybeForceBuilderInitialization(); + } private Builder(com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) {} } @java.lang.Override @@ -498,6 +642,8 @@ public Builder clear() { } typeAnnotation_ = 0; + protoTypeFqn_ = ""; + return this; } @@ -535,6 +681,7 @@ public com.google.spanner.v1.Type buildPartial() { result.structType_ = structTypeBuilder_.build(); } result.typeAnnotation_ = typeAnnotation_; + result.protoTypeFqn_ = protoTypeFqn_; onBuilt(); return result; } @@ -596,7 +743,11 @@ public Builder mergeFrom(com.google.spanner.v1.Type other) { if (other.typeAnnotation_ != 0) { setTypeAnnotationValue(other.getTypeAnnotationValue()); } - this.mergeUnknownFields(other.getUnknownFields()); + if (!other.getProtoTypeFqn().isEmpty()) { + protoTypeFqn_ = other.protoTypeFqn_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); onChanged(); return this; } @@ -611,56 +762,17 @@ public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } + com.google.spanner.v1.Type parsedMessage = null; try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 8: - { - code_ = input.readEnum(); - - break; - } // case 8 - case 18: - { - input.readMessage( - getArrayElementTypeFieldBuilder().getBuilder(), extensionRegistry); - - break; - } // case 18 - case 26: - { - input.readMessage(getStructTypeFieldBuilder().getBuilder(), extensionRegistry); - - break; - } // case 26 - case 32: - { - typeAnnotation_ = input.readEnum(); - - break; - } // case 32 - default: - { - if (!super.parseUnknownField(input, extensionRegistry, tag)) { - done = true; // was an endgroup tag - } - break; - } // default: - } // switch (tag) - } // while (!done) + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.google.spanner.v1.Type) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { - onChanged(); - } // finally + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } return this; } @@ -1258,6 +1370,82 @@ public Builder clearTypeAnnotation() { return this; } + private java.lang.Object protoTypeFqn_ = ""; + /** + * string proto_type_fqn = 5; + * + * @return The protoTypeFqn. + */ + public java.lang.String getProtoTypeFqn() { + java.lang.Object ref = protoTypeFqn_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + protoTypeFqn_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string proto_type_fqn = 5; + * + * @return The bytes for protoTypeFqn. + */ + public com.google.protobuf.ByteString getProtoTypeFqnBytes() { + java.lang.Object ref = protoTypeFqn_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + protoTypeFqn_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string proto_type_fqn = 5; + * + * @param value The protoTypeFqn to set. + * @return This builder for chaining. + */ + public Builder setProtoTypeFqn(java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + protoTypeFqn_ = value; + onChanged(); + return this; + } + /** + * string proto_type_fqn = 5; + * + * @return This builder for chaining. + */ + public Builder clearProtoTypeFqn() { + + protoTypeFqn_ = getDefaultInstance().getProtoTypeFqn(); + onChanged(); + return this; + } + /** + * string proto_type_fqn = 5; + * + * @param value The bytes for protoTypeFqn to set. + * @return This builder for chaining. + */ + public Builder setProtoTypeFqnBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + protoTypeFqn_ = value; + onChanged(); + return this; + } + @java.lang.Override public final Builder setUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); @@ -1290,18 +1478,7 @@ public Type parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (com.google.protobuf.UninitializedMessageException e) { - throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); + return new Type(input, extensionRegistry); } }; diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeCode.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeCode.java index 4b6561e9def..7702f2257b4 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeCode.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeCode.java @@ -177,6 +177,10 @@ public enum TypeCode implements com.google.protobuf.ProtocolMessageEnum { * JSON = 11; */ JSON(11), + /** PROTO = 13; */ + PROTO(13), + /** ENUM = 14; */ + ENUM(14), UNRECOGNIZED(-1), ; @@ -324,6 +328,10 @@ public enum TypeCode implements com.google.protobuf.ProtocolMessageEnum { * JSON = 11; */ public static final int JSON_VALUE = 11; + /** PROTO = 13; */ + public static final int PROTO_VALUE = 13; + /** ENUM = 14; */ + public static final int ENUM_VALUE = 14; public final int getNumber() { if (this == UNRECOGNIZED) { @@ -373,6 +381,10 @@ public static TypeCode forNumber(int value) { return NUMERIC; case 11: return JSON; + case 13: + return PROTO; + case 14: + return ENUM; default: return null; } diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeOrBuilder.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeOrBuilder.java index 4c78a470d6d..0c705016caf 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeOrBuilder.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeOrBuilder.java @@ -158,4 +158,17 @@ public interface TypeOrBuilder * @return The typeAnnotation. */ com.google.spanner.v1.TypeAnnotationCode getTypeAnnotation(); + + /** + * string proto_type_fqn = 5; + * + * @return The protoTypeFqn. + */ + java.lang.String getProtoTypeFqn(); + /** + * string proto_type_fqn = 5; + * + * @return The bytes for protoTypeFqn. + */ + com.google.protobuf.ByteString getProtoTypeFqnBytes(); } diff --git a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeProto.java b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeProto.java index ebca4e75d38..63aa67d3fee 100644 --- a/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeProto.java +++ b/proto-google-cloud-spanner-v1/src/main/java/com/google/spanner/v1/TypeProto.java @@ -50,27 +50,28 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { java.lang.String[] descriptorData = { "\n\034google/spanner/v1/type.proto\022\021google.s" + "panner.v1\032\037google/api/field_behavior.pro" - + "to\"\337\001\n\004Type\022.\n\004code\030\001 \001(\0162\033.google.spann" + + "to\"\367\001\n\004Type\022.\n\004code\030\001 \001(\0162\033.google.spann" + "er.v1.TypeCodeB\003\340A\002\0223\n\022array_element_typ" + "e\030\002 \001(\0132\027.google.spanner.v1.Type\0222\n\013stru" + "ct_type\030\003 \001(\0132\035.google.spanner.v1.Struct" + "Type\022>\n\017type_annotation\030\004 \001(\0162%.google.s" - + "panner.v1.TypeAnnotationCode\"\177\n\nStructTy" - + "pe\0223\n\006fields\030\001 \003(\0132#.google.spanner.v1.S" - + "tructType.Field\032<\n\005Field\022\014\n\004name\030\001 \001(\t\022%" - + "\n\004type\030\002 \001(\0132\027.google.spanner.v1.Type*\245\001" - + "\n\010TypeCode\022\031\n\025TYPE_CODE_UNSPECIFIED\020\000\022\010\n" - + "\004BOOL\020\001\022\t\n\005INT64\020\002\022\013\n\007FLOAT64\020\003\022\r\n\tTIMES" - + "TAMP\020\004\022\010\n\004DATE\020\005\022\n\n\006STRING\020\006\022\t\n\005BYTES\020\007\022" - + "\t\n\005ARRAY\020\010\022\n\n\006STRUCT\020\t\022\013\n\007NUMERIC\020\n\022\010\n\004J" - + "SON\020\013*X\n\022TypeAnnotationCode\022$\n TYPE_ANNO" - + "TATION_CODE_UNSPECIFIED\020\000\022\016\n\nPG_NUMERIC\020" - + "\002\022\014\n\010PG_JSONB\020\003B\257\001\n\025com.google.spanner.v" - + "1B\tTypeProtoP\001Z8google.golang.org/genpro" - + "to/googleapis/spanner/v1;spanner\252\002\027Googl" - + "e.Cloud.Spanner.V1\312\002\027Google\\Cloud\\Spanne" - + "r\\V1\352\002\032Google::Cloud::Spanner::V1b\006proto" - + "3" + + "panner.v1.TypeAnnotationCode\022\026\n\016proto_ty" + + "pe_fqn\030\005 \001(\t\"\177\n\nStructType\0223\n\006fields\030\001 \003" + + "(\0132#.google.spanner.v1.StructType.Field\032" + + "<\n\005Field\022\014\n\004name\030\001 \001(\t\022%\n\004type\030\002 \001(\0132\027.g" + + "oogle.spanner.v1.Type*\272\001\n\010TypeCode\022\031\n\025TY" + + "PE_CODE_UNSPECIFIED\020\000\022\010\n\004BOOL\020\001\022\t\n\005INT64" + + "\020\002\022\013\n\007FLOAT64\020\003\022\r\n\tTIMESTAMP\020\004\022\010\n\004DATE\020\005" + + "\022\n\n\006STRING\020\006\022\t\n\005BYTES\020\007\022\t\n\005ARRAY\020\010\022\n\n\006ST" + + "RUCT\020\t\022\013\n\007NUMERIC\020\n\022\010\n\004JSON\020\013\022\t\n\005PROTO\020\r" + + "\022\010\n\004ENUM\020\016*X\n\022TypeAnnotationCode\022$\n TYPE" + + "_ANNOTATION_CODE_UNSPECIFIED\020\000\022\016\n\nPG_NUM" + + "ERIC\020\002\022\014\n\010PG_JSONB\020\003B\257\001\n\025com.google.span" + + "ner.v1B\tTypeProtoP\001Z8google.golang.org/g" + + "enproto/googleapis/spanner/v1;spanner\252\002\027" + + "Google.Cloud.Spanner.V1\312\002\027Google\\Cloud\\S" + + "panner\\V1\352\002\032Google::Cloud::Spanner::V1b\006" + + "proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( @@ -83,7 +84,7 @@ public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_google_spanner_v1_Type_descriptor, new java.lang.String[] { - "Code", "ArrayElementType", "StructType", "TypeAnnotation", + "Code", "ArrayElementType", "StructType", "TypeAnnotation", "ProtoTypeFqn", }); internal_static_google_spanner_v1_StructType_descriptor = getDescriptor().getMessageTypes().get(1); diff --git a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto index 712ec187fcc..0f2cd64ebad 100644 --- a/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto +++ b/proto-google-cloud-spanner-v1/src/main/proto/google/spanner/v1/type.proto @@ -47,6 +47,11 @@ message Type { // typically is not needed to process the content of a value (it doesn't // affect serialization) and clients can ignore it on the read path. TypeAnnotationCode type_annotation = 4; + + // If [code][] == [PROTO][TypeCode.PROTO] or [code][] == + // [ENUM][TypeCode.ENUM], then `proto_type_fqn` is the fully qualified name of + // the proto type representing the proto/enum definition. + string proto_type_fqn = 5; } // `StructType` defines the fields of a [STRUCT][google.spanner.v1.TypeCode.STRUCT] type. @@ -145,6 +150,13 @@ enum TypeCode { // preserved. // - JSON array elements will have their order preserved. JSON = 11; + + // Encoded as a base64-encoded `string`, as described in RFC 4648, + // section 4. + PROTO = 13; + + // Encoded as `string`, in decimal format. + ENUM = 14; } // `TypeAnnotationCode` is used as a part of [Type][google.spanner.v1.Type] to