Skip to content

Commit cbabd6c

Browse files
feat: Support for Proto Messages & Enums (#2155)
* feat: Importing Proto Changes Commit will be reverted, once PROTO changes are available publicly. * feat: Proto Message Implementation * feat: Adding support for enum * feat: Code refactoring Adding default implementation for newly added methods ByteArray compatability changes for Proto Messages * docs: Adding Java docs for all the newly added methods. * test: Sample Proto & Generated classes for unit test * feat: Adding bytes/proto & int64/enum compatability Adding Additional check for ChecksumResultSet * test: Adding unit tests * test: Adding unit tests for ValueBinder.java * feat: refactoring to add support for getValue & other minor changes * feat: Minor refactoring 1. Adding docs and formatting the code. 2. Adding additional methods for enum and message which accepts descriptors. * feat: Adding bytes/message & int64/enum compatability in Value * refactor: Minor refactoring * feat: Adding Proto Array Implementation * test: Implementing unit tests for array of protos and enums * refactor: adding clirr ignores * feat: Adding support for enum as Primary Key * feat: Code Review Changes, minor refactoring and adding docs * feat: Addressing review comments -Modified Docs/Comments -Minor Refactoring * refactor: Using Column instead of column to avoid test failures * feat: Minor refactoring -code review comments -adding function docs
1 parent f853918 commit cbabd6c

33 files changed

+3520
-175
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,46 @@
192192
<className>com/google/cloud/spanner/StructReader</className>
193193
<method>java.util.List getPgJsonbList(java.lang.String)</method>
194194
</difference>
195+
<difference>
196+
<differenceType>7012</differenceType>
197+
<className>com/google/cloud/spanner/StructReader</className>
198+
<method>com.google.protobuf.ProtocolMessageEnum getProtoEnum(int, java.util.function.Function)</method>
199+
</difference>
200+
<difference>
201+
<differenceType>7012</differenceType>
202+
<className>com/google/cloud/spanner/StructReader</className>
203+
<method>com.google.protobuf.ProtocolMessageEnum getProtoEnum(java.lang.String, java.util.function.Function)</method>
204+
</difference>
205+
<difference>
206+
<differenceType>7012</differenceType>
207+
<className>com/google/cloud/spanner/StructReader</className>
208+
<method>com.google.protobuf.AbstractMessage getProtoMessage(int, com.google.protobuf.AbstractMessage)</method>
209+
</difference>
210+
<difference>
211+
<differenceType>7012</differenceType>
212+
<className>com/google/cloud/spanner/StructReader</className>
213+
<method>com.google.protobuf.AbstractMessage getProtoMessage(java.lang.String, com.google.protobuf.AbstractMessage)</method>
214+
</difference>
215+
<difference>
216+
<differenceType>7012</differenceType>
217+
<className>com/google/cloud/spanner/StructReader</className>
218+
<method>java.util.List getProtoEnumList(int, java.util.function.Function)</method>
219+
</difference>
220+
<difference>
221+
<differenceType>7012</differenceType>
222+
<className>com/google/cloud/spanner/StructReader</className>
223+
<method>java.util.List getProtoEnumList(java.lang.String, java.util.function.Function)</method>
224+
</difference>
225+
<difference>
226+
<differenceType>7012</differenceType>
227+
<className>com/google/cloud/spanner/StructReader</className>
228+
<method>java.util.List getProtoMessageList(int, com.google.protobuf.AbstractMessage)</method>
229+
</difference>
230+
<difference>
231+
<differenceType>7012</differenceType>
232+
<className>com/google/cloud/spanner/StructReader</className>
233+
<method>java.util.List getProtoMessageList(java.lang.String, com.google.protobuf.AbstractMessage)</method>
234+
</difference>
195235
<difference>
196236
<differenceType>7012</differenceType>
197237
<className>com/google/cloud/spanner/BatchClient</className>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,16 @@
3232
import com.google.cloud.spanner.spi.v1.SpannerRpc;
3333
import com.google.cloud.spanner.v1.stub.SpannerStubSettings;
3434
import com.google.common.annotations.VisibleForTesting;
35+
import com.google.common.base.Preconditions;
3536
import com.google.common.collect.AbstractIterator;
3637
import com.google.common.collect.ImmutableMap;
3738
import com.google.common.collect.Lists;
3839
import com.google.common.util.concurrent.Uninterruptibles;
40+
import com.google.protobuf.AbstractMessage;
3941
import com.google.protobuf.ByteString;
42+
import com.google.protobuf.InvalidProtocolBufferException;
4043
import com.google.protobuf.ListValue;
44+
import com.google.protobuf.ProtocolMessageEnum;
4145
import com.google.protobuf.Value.KindCase;
4246
import com.google.spanner.v1.PartialResultSet;
4347
import com.google.spanner.v1.ResultSetMetadata;
@@ -65,6 +69,7 @@
6569
import java.util.concurrent.Executor;
6670
import java.util.concurrent.LinkedBlockingQueue;
6771
import java.util.concurrent.TimeUnit;
72+
import java.util.function.Function;
6873
import java.util.logging.Level;
6974
import java.util.logging.Logger;
7075
import javax.annotation.Nullable;
@@ -391,6 +396,14 @@ private Object writeReplace() {
391396
case JSON:
392397
builder.set(fieldName).to(Value.json((String) value));
393398
break;
399+
case PROTO:
400+
builder
401+
.set(fieldName)
402+
.to(Value.protoMessage((ByteArray) value, fieldType.getProtoTypeFqn()));
403+
break;
404+
case ENUM:
405+
builder.set(fieldName).to(Value.protoEnum((Long) value, fieldType.getProtoTypeFqn()));
406+
break;
394407
case PG_JSONB:
395408
builder.set(fieldName).to(Value.pgJsonb((String) value));
396409
break;
@@ -410,6 +423,7 @@ private Object writeReplace() {
410423
builder.set(fieldName).toBoolArray((Iterable<Boolean>) value);
411424
break;
412425
case INT64:
426+
case ENUM:
413427
builder.set(fieldName).toInt64Array((Iterable<Long>) value);
414428
break;
415429
case FLOAT64:
@@ -431,6 +445,7 @@ private Object writeReplace() {
431445
builder.set(fieldName).toPgJsonbArray((Iterable<String>) value);
432446
break;
433447
case BYTES:
448+
case PROTO:
434449
builder.set(fieldName).toBytesArray((Iterable<ByteArray>) value);
435450
break;
436451
case TIMESTAMP:
@@ -496,6 +511,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
496511
checkType(fieldType, proto, KindCase.BOOL_VALUE);
497512
return proto.getBoolValue();
498513
case INT64:
514+
case ENUM:
499515
checkType(fieldType, proto, KindCase.STRING_VALUE);
500516
return Long.parseLong(proto.getStringValue());
501517
case FLOAT64:
@@ -510,6 +526,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
510526
checkType(fieldType, proto, KindCase.STRING_VALUE);
511527
return proto.getStringValue();
512528
case BYTES:
529+
case PROTO:
513530
checkType(fieldType, proto, KindCase.STRING_VALUE);
514531
return ByteArray.fromBase64(proto.getStringValue());
515532
case TIMESTAMP:
@@ -547,7 +564,8 @@ private static Struct decodeStructValue(Type structType, ListValue structValue)
547564
static Object decodeArrayValue(Type elementType, ListValue listValue) {
548565
switch (elementType.getCode()) {
549566
case INT64:
550-
// For int64/float64 types, use custom containers. These avoid wrapper object
567+
case ENUM:
568+
// For int64/float64/enum types, use custom containers. These avoid wrapper object
551569
// creation for non-null arrays.
552570
return new Int64Array(listValue);
553571
case FLOAT64:
@@ -562,6 +580,7 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) {
562580
case TIMESTAMP:
563581
case DATE:
564582
case STRUCT:
583+
case PROTO:
565584
return Lists.transform(
566585
listValue.getValuesList(), input -> decodeValue(elementType, input));
567586
default:
@@ -597,6 +616,30 @@ public boolean isNull(int columnIndex) {
597616
return rowData.get(columnIndex) == null;
598617
}
599618

619+
@Override
620+
protected <T extends AbstractMessage> T getProtoMessageInternal(int columnIndex, T message) {
621+
Preconditions.checkNotNull(
622+
message,
623+
"Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value.");
624+
try {
625+
return (T)
626+
message
627+
.toBuilder()
628+
.mergeFrom(((ByteArray) rowData.get(columnIndex)).toByteArray())
629+
.build();
630+
} catch (InvalidProtocolBufferException e) {
631+
throw SpannerExceptionFactory.asSpannerException(e);
632+
}
633+
}
634+
635+
@Override
636+
protected <T extends ProtocolMessageEnum> T getProtoEnumInternal(
637+
int columnIndex, Function<Integer, ProtocolMessageEnum> method) {
638+
Preconditions.checkNotNull(
639+
method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value.");
640+
return (T) method.apply((int) getLongInternal(columnIndex));
641+
}
642+
600643
@Override
601644
protected boolean getBooleanInternal(int columnIndex) {
602645
return (Boolean) rowData.get(columnIndex);
@@ -658,6 +701,8 @@ protected Value getValueInternal(int columnIndex) {
658701
return Value.bool(isNull ? null : getBooleanInternal(columnIndex));
659702
case INT64:
660703
return Value.int64(isNull ? null : getLongInternal(columnIndex));
704+
case ENUM:
705+
return Value.protoEnum(getLongInternal(columnIndex), columnType.getProtoTypeFqn());
661706
case NUMERIC:
662707
return Value.numeric(isNull ? null : getBigDecimalInternal(columnIndex));
663708
case PG_NUMERIC:
@@ -672,6 +717,8 @@ protected Value getValueInternal(int columnIndex) {
672717
return Value.pgJsonb(isNull ? null : getPgJsonbInternal(columnIndex));
673718
case BYTES:
674719
return Value.bytes(isNull ? null : getBytesInternal(columnIndex));
720+
case PROTO:
721+
return Value.protoMessage(getBytesInternal(columnIndex), columnType.getProtoTypeFqn());
675722
case TIMESTAMP:
676723
return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex));
677724
case DATE:
@@ -699,6 +746,12 @@ protected Value getValueInternal(int columnIndex) {
699746
return Value.pgJsonbArray(isNull ? null : getPgJsonbListInternal(columnIndex));
700747
case BYTES:
701748
return Value.bytesArray(isNull ? null : getBytesListInternal(columnIndex));
749+
case PROTO:
750+
return Value.protoMessageArray(
751+
isNull ? null : getBytesListInternal(columnIndex), elementType.getProtoTypeFqn());
752+
case ENUM:
753+
return Value.protoEnumArray(
754+
isNull ? null : getLongListInternal(columnIndex), elementType.getProtoTypeFqn());
702755
case TIMESTAMP:
703756
return Value.timestampArray(isNull ? null : getTimestampListInternal(columnIndex));
704757
case DATE:
@@ -778,6 +831,52 @@ protected List<String> getJsonListInternal(int columnIndex) {
778831
return Collections.unmodifiableList((List<String>) rowData.get(columnIndex));
779832
}
780833

834+
@Override
835+
@SuppressWarnings("unchecked") // We know ARRAY<PROTO> produces a List<ByteArray>.
836+
protected <T extends AbstractMessage> List<T> getProtoMessageListInternal(
837+
int columnIndex, T message) {
838+
Preconditions.checkNotNull(
839+
message,
840+
"Proto message may not be null. Use MyProtoClass.getDefaultInstance() as a parameter value.");
841+
842+
List<ByteArray> bytesArray = (List<ByteArray>) rowData.get(columnIndex);
843+
844+
try {
845+
List<T> protoMessagesList = new ArrayList<>(bytesArray.size());
846+
for (ByteArray protoMessageBytes : bytesArray) {
847+
if (protoMessageBytes == null) {
848+
protoMessagesList.add(null);
849+
} else {
850+
protoMessagesList.add(
851+
(T) message.toBuilder().mergeFrom(protoMessageBytes.toByteArray()).build());
852+
}
853+
}
854+
return protoMessagesList;
855+
} catch (InvalidProtocolBufferException e) {
856+
throw SpannerExceptionFactory.asSpannerException(e);
857+
}
858+
}
859+
860+
@Override
861+
@SuppressWarnings("unchecked") // We know ARRAY<ENUM> produces a List<Long>.
862+
protected <T extends ProtocolMessageEnum> List<T> getProtoEnumListInternal(
863+
int columnIndex, Function<Integer, ProtocolMessageEnum> method) {
864+
Preconditions.checkNotNull(
865+
method, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value.");
866+
867+
List<Long> enumIntArray = (List<Long>) rowData.get(columnIndex);
868+
List<T> protoEnumList = new ArrayList<>(enumIntArray.size());
869+
for (Long enumIntValue : enumIntArray) {
870+
if (enumIntValue == null) {
871+
protoEnumList.add(null);
872+
} else {
873+
protoEnumList.add((T) method.apply(enumIntValue.intValue()));
874+
}
875+
}
876+
877+
return protoEnumList;
878+
}
879+
781880
@Override
782881
@SuppressWarnings("unchecked") // We know ARRAY<JSONB> produces a List<String>.
783882
protected List<String> getPgJsonbListInternal(int columnIndex) {
@@ -1310,6 +1409,17 @@ protected String getStringInternal(int columnIndex) {
13101409
return currRow().getStringInternal(columnIndex);
13111410
}
13121411

1412+
@Override
1413+
protected <T extends AbstractMessage> T getProtoMessageInternal(int columnIndex, T message) {
1414+
return currRow().getProtoMessageInternal(columnIndex, message);
1415+
}
1416+
1417+
@Override
1418+
protected <T extends ProtocolMessageEnum> T getProtoEnumInternal(
1419+
int columnIndex, Function<Integer, ProtocolMessageEnum> method) {
1420+
return currRow().getProtoEnumInternal(columnIndex, method);
1421+
}
1422+
13131423
@Override
13141424
protected String getJsonInternal(int columnIndex) {
13151425
return currRow().getJsonInternal(columnIndex);
@@ -1395,6 +1505,18 @@ protected List<ByteArray> getBytesListInternal(int columnIndex) {
13951505
return currRow().getBytesListInternal(columnIndex);
13961506
}
13971507

1508+
@Override
1509+
protected <T extends AbstractMessage> List<T> getProtoMessageListInternal(
1510+
int columnIndex, T message) {
1511+
return currRow().getProtoMessageListInternal(columnIndex, message);
1512+
}
1513+
1514+
@Override
1515+
protected <T extends ProtocolMessageEnum> List<T> getProtoEnumListInternal(
1516+
int columnIndex, Function<Integer, ProtocolMessageEnum> method) {
1517+
return currRow().getProtoEnumListInternal(columnIndex, method);
1518+
}
1519+
13981520
@Override
13991521
protected List<Timestamp> getTimestampListInternal(int columnIndex) {
14001522
return currRow().getTimestampListInternal(columnIndex);

0 commit comments

Comments
 (0)