Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 7927687

Browse files
committed
feat: Add support for Proto and Enum types
Change-Id: I27c4d06a3e29bb52b0e5391acba9730c05997164
1 parent 9d1d1e2 commit 7927687

15 files changed

Lines changed: 1065 additions & 9 deletions

File tree

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/common/Type.java

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@
2121
import com.google.cloud.bigtable.data.v2.internal.ColumnToIndexMapper;
2222
import com.google.cloud.bigtable.data.v2.models.sql.SqlType;
2323
import com.google.common.base.Objects;
24+
import com.google.common.base.Preconditions;
2425
import com.google.common.collect.ImmutableList;
26+
import com.google.protobuf.AbstractMessage;
2527
import com.google.protobuf.ByteString;
28+
import com.google.protobuf.Parser;
29+
import com.google.protobuf.ProtocolMessageEnum;
2630
import java.time.Instant;
2731
import java.util.List;
32+
import java.util.function.Function;
33+
import javax.annotation.Nonnull;
2834

2935
/**
3036
* Shared type implementations. Right now this is only used by SqlType but this will become a shared
@@ -385,6 +391,184 @@ public java.lang.String toString() {
385391
}
386392
}
387393

394+
@AutoValue
395+
abstract class Proto<T extends AbstractMessage> implements Type, SqlType.Proto<T> {
396+
397+
public static <T extends AbstractMessage> SqlType.Proto<T> create(T message) {
398+
Preconditions.checkNotNull(
399+
message,
400+
"Proto message may not be null. Use 'MyProtoMessage::getDefaultInstance()' as a parameter value.");
401+
return new AutoValue_Type_Proto<>(message);
402+
}
403+
404+
@Nonnull
405+
abstract T message();
406+
407+
@Override
408+
public Code getCode() {
409+
return Code.PROTO;
410+
}
411+
412+
@Nonnull
413+
@Override
414+
public Parser<T> getParserForType() {
415+
return (Parser<T>) message().getParserForType();
416+
}
417+
418+
@Override
419+
public java.lang.String getMessageName() {
420+
return message().getDescriptorForType().getFullName();
421+
}
422+
423+
@Override
424+
public java.lang.String toString() {
425+
return getCode().name() + "{message=" + getMessageName() + "}";
426+
}
427+
}
428+
429+
@AutoValue
430+
abstract class Enum<T extends ProtocolMessageEnum> implements Type, SqlType.Enum<T> {
431+
432+
public static <T extends ProtocolMessageEnum> SqlType.Enum<T> create(
433+
Function<Integer, T> forNumber) {
434+
Preconditions.checkNotNull(
435+
forNumber, "Method may not be null. Use 'MyProtoEnum::forNumber' as a parameter value.");
436+
return new AutoValue_Type_Enum<>(forNumber);
437+
}
438+
439+
@Nonnull
440+
@Override
441+
public abstract Function<Integer, T> getForNumber();
442+
443+
@Override
444+
public Code getCode() {
445+
return Code.ENUM;
446+
}
447+
448+
@Override
449+
public java.lang.String toString() {
450+
T thisEnum = getForNumber().apply(0);
451+
if (thisEnum == null) {
452+
return getCode().name() + "{function=" + getForNumber() + "}";
453+
}
454+
return getCode().name() + "{enum=" + thisEnum.getDescriptorForType().getFullName() + "}";
455+
}
456+
457+
@Override
458+
public boolean equals(Object o) {
459+
if (this == o) {
460+
return true;
461+
}
462+
if (!(o instanceof Type.Enum)) {
463+
return false;
464+
}
465+
Type.Enum<?> that = (Type.Enum<?>) o;
466+
// We don't want to compare functions directly, so try to get the enum descriptor and compare
467+
// those.
468+
T thisEnum = getForNumber().apply(0);
469+
Object thatEnum = that.getForNumber().apply(0);
470+
471+
if (thisEnum == null || thatEnum == null) {
472+
// Can't determine equality, fallback to object equality on the function.
473+
return getForNumber().equals(that.getForNumber());
474+
}
475+
return thisEnum
476+
.getDescriptorForType()
477+
.getFullName()
478+
.equals(((ProtocolMessageEnum) thatEnum).getDescriptorForType().getFullName());
479+
}
480+
481+
@Override
482+
public int hashCode() {
483+
T thisEnum = getForNumber().apply(0);
484+
if (thisEnum == null) {
485+
return getForNumber().hashCode();
486+
}
487+
return java.util.Objects.hash(getCode(), thisEnum.getDescriptorForType().getFullName());
488+
}
489+
}
490+
491+
/**
492+
* This is a special version of proto that is intended to only be used in the {@link
493+
* com.google.cloud.bigtable.data.v2.models.sql.StructReader} getters that require types. We don't
494+
* want users to need to specify the proto schema when the schema will be validated on calls to
495+
* {@link com.google.cloud.bigtable.data.v2.models.sql.StructReader} methods on the struct.
496+
*
497+
* <p>Any attempts to call getParserForType() will throw an exception.
498+
*/
499+
@AutoValue
500+
abstract class SchemalessProto implements SqlType.Proto {
501+
502+
public static SchemalessProto fromProto(com.google.bigtable.v2.Type.Proto proto) {
503+
return create(proto.getMessageName());
504+
}
505+
506+
public static SchemalessProto create(java.lang.String messageName) {
507+
return new AutoValue_Type_SchemalessProto(messageName);
508+
}
509+
510+
abstract java.lang.String messageName();
511+
512+
@Override
513+
public Parser<AbstractMessage> getParserForType() {
514+
throw new UnsupportedOperationException(
515+
"Cannot get parser for unresolved proto type. Please use the getProtoMessage overload that takes a message instance.");
516+
}
517+
518+
@Override
519+
public java.lang.String getMessageName() {
520+
return messageName();
521+
}
522+
523+
@Override
524+
public Code getCode() {
525+
return Code.PROTO;
526+
}
527+
528+
@Override
529+
public java.lang.String toString() {
530+
return getCode().name() + "{messageName=" + messageName() + "}";
531+
}
532+
}
533+
534+
/**
535+
* This is a special version of enum that is intended to only be used in the {@link
536+
* com.google.cloud.bigtable.data.v2.models.sql.StructReader} getters that require types. We don't
537+
* want users to need to specify the enum schema when the schema will be validated on calls to
538+
* {@link com.google.cloud.bigtable.data.v2.models.sql.StructReader} methods on the struct.
539+
*
540+
* <p>Any attempts to call getForNumber() will throw an exception.
541+
*/
542+
@AutoValue
543+
abstract class SchemalessEnum implements SqlType.Enum {
544+
545+
public static SchemalessEnum fromProto(com.google.bigtable.v2.Type.Enum proto) {
546+
return create(proto.getEnumName());
547+
}
548+
549+
public static SchemalessEnum create(java.lang.String enumName) {
550+
return new AutoValue_Type_SchemalessEnum(enumName);
551+
}
552+
553+
abstract java.lang.String enumName();
554+
555+
@Override
556+
public Function<Integer, ProtocolMessageEnum> getForNumber() {
557+
throw new UnsupportedOperationException(
558+
"Cannot get forNumber for unresolved enum type. Please use the getProtoEnum overload that takes a forNumber function.");
559+
}
560+
561+
@Override
562+
public Code getCode() {
563+
return Code.ENUM;
564+
}
565+
566+
@Override
567+
public java.lang.String toString() {
568+
return getCode().name() + "{enumName=" + enumName() + "}";
569+
}
570+
}
571+
388572
// Implementation detail to make singleton instances private without referencing the concrete
389573
// autovalue generated class from the abstract base classes.
390574
@InternalApi

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReader.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@
2323
import com.google.cloud.bigtable.data.v2.models.sql.Struct;
2424
import com.google.cloud.bigtable.data.v2.models.sql.StructReader;
2525
import com.google.common.base.Preconditions;
26+
import com.google.protobuf.AbstractMessage;
2627
import com.google.protobuf.ByteString;
28+
import com.google.protobuf.InvalidProtocolBufferException;
29+
import com.google.protobuf.ProtocolMessageEnum;
2730
import java.time.Instant;
2831
import java.util.ArrayList;
2932
import java.util.Collections;
3033
import java.util.HashMap;
3134
import java.util.List;
3235
import java.util.Map;
36+
import java.util.function.Function;
3337

3438
@InternalApi
3539
public abstract class AbstractProtoStructReader implements StructReader {
@@ -220,6 +224,12 @@ public <ElemType> List<ElemType> getList(int columnIndex, SqlType.Array<ElemType
220224
SqlType<?> actualType = getColumnType(columnIndex);
221225
checkNonNullOfType(columnIndex, arrayType, actualType, columnIndex);
222226
Value value = values().get(columnIndex);
227+
// If the element type is proto/enum, we should use the user passed type, which contains the
228+
// schema. Otherwise, we should use the type from metadata.
229+
SqlType<?> elementType = arrayType.getElementType();
230+
if (elementType.getCode() == SqlType.Code.PROTO || elementType.getCode() == SqlType.Code.ENUM) {
231+
return (List<ElemType>) decodeValue(value, arrayType);
232+
}
223233
return (List<ElemType>) decodeValue(value, actualType);
224234
}
225235

@@ -231,6 +241,12 @@ public <ElemType> List<ElemType> getList(String columnName, SqlType.Array<ElemTy
231241
SqlType<?> actualType = getColumnType(columnIndex);
232242
checkNonNullOfType(columnIndex, arrayType, actualType, columnName);
233243
Value value = values().get(columnIndex);
244+
// If the element type is proto/enum, we should use the user passed type, which contains the
245+
// schema. Otherwise, we should use the type from metadata.
246+
SqlType<?> elementType = arrayType.getElementType();
247+
if (elementType.getCode() == SqlType.Code.PROTO || elementType.getCode() == SqlType.Code.ENUM) {
248+
return (List<ElemType>) decodeValue(value, arrayType);
249+
}
234250
return (List<ElemType>) decodeValue(value, actualType);
235251
}
236252

@@ -241,6 +257,12 @@ public <K, V> Map<K, V> getMap(int columnIndex, SqlType.Map<K, V> mapType) {
241257
SqlType<?> actualType = getColumnType(columnIndex);
242258
checkNonNullOfType(columnIndex, mapType, actualType, columnIndex);
243259
Value value = values().get(columnIndex);
260+
// If the value type is proto/enum, we should use the user passed type, which contains the
261+
// schema. Otherwise, we should use the type from metadata.
262+
SqlType<?> valueType = mapType.getValueType();
263+
if (valueType.getCode() == SqlType.Code.PROTO || valueType.getCode() == SqlType.Code.ENUM) {
264+
return (Map<K, V>) decodeValue(value, mapType);
265+
}
244266
return (Map<K, V>) decodeValue(value, actualType);
245267
}
246268

@@ -252,9 +274,61 @@ public <K, V> Map<K, V> getMap(String columnName, SqlType.Map<K, V> mapType) {
252274
SqlType<?> actualType = getColumnType(columnIndex);
253275
checkNonNullOfType(columnIndex, mapType, actualType, columnName);
254276
Value value = values().get(columnIndex);
277+
// If the value type is proto/enum, we should use the user passed type, which contains the
278+
// schema. Otherwise, we should use the type from metadata.
279+
SqlType<?> valueType = mapType.getValueType();
280+
if (valueType.getCode() == SqlType.Code.PROTO || valueType.getCode() == SqlType.Code.ENUM) {
281+
return (Map<K, V>) decodeValue(value, mapType);
282+
}
255283
return (Map<K, V>) decodeValue(value, actualType);
256284
}
257285

286+
@Override
287+
public <MsgType extends AbstractMessage> MsgType getProtoMessage(
288+
int columnIndex, MsgType message) {
289+
// Note it is import that we use the user passed message object to decode, because the type in
290+
// the corresponding column metadata only have a message name and doesn't have schemas
291+
SqlType.Proto<MsgType> actualType = SqlType.protoOf(message);
292+
checkNonNullOfType(columnIndex, getColumnType(columnIndex), actualType, columnIndex);
293+
Value value = values().get(columnIndex);
294+
return (MsgType) decodeValue(value, actualType);
295+
}
296+
297+
@Override
298+
public <MsgType extends AbstractMessage> MsgType getProtoMessage(
299+
String columnName, MsgType message) {
300+
int columnIndex = getColumnIndex(columnName);
301+
// Note it is import that we use the user passed message object to decode, because the type in
302+
// the corresponding column metadata only have a message name and doesn't have schemas
303+
SqlType.Proto<MsgType> actualType = SqlType.protoOf(message);
304+
checkNonNullOfType(columnIndex, getColumnType(columnIndex), actualType, columnName);
305+
Value value = values().get(columnIndex);
306+
return (MsgType) decodeValue(value, actualType);
307+
}
308+
309+
@Override
310+
public <EnumType extends ProtocolMessageEnum> EnumType getProtoEnum(
311+
int columnIndex, Function<Integer, EnumType> forNumber) {
312+
// Note it is import that we use the user passed function to decode, because the type in
313+
// the corresponding column metadata only have an enum message name and doesn't have schemas
314+
SqlType.Enum<EnumType> actualType = SqlType.enumOf(forNumber);
315+
checkNonNullOfType(columnIndex, getColumnType(columnIndex), actualType, columnIndex);
316+
Value value = values().get(columnIndex);
317+
return (EnumType) decodeValue(value, actualType);
318+
}
319+
320+
@Override
321+
public <EnumType extends ProtocolMessageEnum> EnumType getProtoEnum(
322+
String columnName, Function<Integer, EnumType> forNumber) {
323+
int columnIndex = getColumnIndex(columnName);
324+
// Note it is import that we use the user passed function to decode, because the type in
325+
// the corresponding column metadata only have an enum message name and doesn't have schemas
326+
SqlType.Enum<EnumType> actualType = SqlType.enumOf(forNumber);
327+
checkNonNullOfType(columnIndex, getColumnType(columnIndex), actualType, columnName);
328+
Value value = values().get(columnIndex);
329+
return (EnumType) decodeValue(value, actualType);
330+
}
331+
258332
Object decodeValue(Value value, SqlType<?> type) {
259333
if (value.getKindCase().equals(KindCase.KIND_NOT_SET)) {
260334
return null;
@@ -281,6 +355,14 @@ Object decodeValue(Value value, SqlType<?> type) {
281355
SqlType.Struct schema = (SqlType.Struct) type;
282356
// A struct value is represented as an array
283357
return ProtoStruct.create(schema, value.getArrayValue());
358+
case PROTO:
359+
try {
360+
return ((SqlType.Proto<?>) type).getParserForType().parseFrom(value.getBytesValue());
361+
} catch (InvalidProtocolBufferException e) {
362+
throw new IllegalStateException("Unable to parse value to proto " + type, e);
363+
}
364+
case ENUM:
365+
return ((SqlType.Enum<?>) type).getForNumber().apply((int) value.getIntValue());
284366
case ARRAY:
285367
ArrayList<Object> listBuilder = new ArrayList<>();
286368
SqlType.Array<?> arrayType = (SqlType.Array<?>) type;

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/ResultSetImpl.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import com.google.cloud.bigtable.data.v2.models.sql.StructReader;
2828
import com.google.cloud.bigtable.data.v2.stub.sql.SqlServerStream;
2929
import com.google.common.base.Preconditions;
30+
import com.google.protobuf.AbstractMessage;
3031
import com.google.protobuf.ByteString;
32+
import com.google.protobuf.ProtocolMessageEnum;
3133
import java.time.Instant;
3234
import java.util.Iterator;
3335
import java.util.List;
3436
import java.util.Map;
37+
import java.util.function.Function;
3538

3639
/**
3740
* The primary implementation of a ResultSet.
@@ -215,4 +218,28 @@ public <K, V> Map<K, V> getMap(int columnIndex, SqlType.Map<K, V> mapType) {
215218
public <K, V> Map<K, V> getMap(String columnName, SqlType.Map<K, V> mapType) {
216219
return getCurrentRow().getMap(columnName, mapType);
217220
}
221+
222+
@Override
223+
public <EnumType extends ProtocolMessageEnum> EnumType getProtoEnum(
224+
int columnIndex, Function<Integer, EnumType> forNumber) {
225+
return getCurrentRow().getProtoEnum(columnIndex, forNumber);
226+
}
227+
228+
@Override
229+
public <EnumType extends ProtocolMessageEnum> EnumType getProtoEnum(
230+
String columnName, Function<Integer, EnumType> forNumber) {
231+
return getCurrentRow().getProtoEnum(columnName, forNumber);
232+
}
233+
234+
@Override
235+
public <MsgType extends AbstractMessage> MsgType getProtoMessage(
236+
int columnIndex, MsgType message) {
237+
return getCurrentRow().getProtoMessage(columnIndex, message);
238+
}
239+
240+
@Override
241+
public <MsgType extends AbstractMessage> MsgType getProtoMessage(
242+
String columnName, MsgType message) {
243+
return getCurrentRow().getProtoMessage(columnName, message);
244+
}
218245
}

0 commit comments

Comments
 (0)