3232import com .google .cloud .spanner .spi .v1 .SpannerRpc ;
3333import com .google .cloud .spanner .v1 .stub .SpannerStubSettings ;
3434import com .google .common .annotations .VisibleForTesting ;
35+ import com .google .common .base .Preconditions ;
3536import com .google .common .collect .AbstractIterator ;
3637import com .google .common .collect .ImmutableMap ;
3738import com .google .common .collect .Lists ;
3839import com .google .common .util .concurrent .Uninterruptibles ;
3940import com .google .protobuf .ByteString ;
4041import com .google .protobuf .ListValue ;
42+ import com .google .protobuf .NullValue ;
4143import com .google .protobuf .Value .KindCase ;
4244import com .google .spanner .v1 .PartialResultSet ;
4345import com .google .spanner .v1 .ResultSetMetadata ;
5557import java .math .BigDecimal ;
5658import java .util .AbstractList ;
5759import java .util .ArrayList ;
60+ import java .util .Base64 ;
5861import java .util .BitSet ;
5962import java .util .Collections ;
6063import java .util .Iterator ;
6164import java .util .LinkedList ;
6265import java .util .List ;
66+ import java .util .Objects ;
6367import java .util .concurrent .BlockingQueue ;
6468import java .util .concurrent .CountDownLatch ;
6569import java .util .concurrent .Executor ;
6670import java .util .concurrent .LinkedBlockingQueue ;
6771import java .util .concurrent .TimeUnit ;
6872import java .util .logging .Level ;
6973import java .util .logging .Logger ;
74+ import java .util .stream .Collectors ;
75+ import javax .annotation .Nonnull ;
7076import javax .annotation .Nullable ;
7177
7278/** Implementation of {@link ResultSet}. */
7379abstract class AbstractResultSet <R > extends AbstractStructReader implements ResultSet {
7480 private static final Tracer tracer = Tracing .getTracer ();
81+ private static final com .google .protobuf .Value NULL_VALUE =
82+ com .google .protobuf .Value .newBuilder ().setNullValue (NullValue .NULL_VALUE ).build ();
7583
7684 interface Listener {
7785 /**
@@ -353,6 +361,79 @@ private boolean isMergeable(KindCase kind) {
353361 }
354362 }
355363
364+ static final class LazyByteArray implements Serializable {
365+ private static final Base64 .Encoder ENCODER = Base64 .getEncoder ();
366+ private static final Base64 .Decoder DECODER = Base64 .getDecoder ();
367+ private final String base64String ;
368+ private transient AbstractLazyInitializer <ByteArray > byteArray ;
369+
370+ LazyByteArray (@ Nonnull String base64String ) {
371+ this .base64String = Preconditions .checkNotNull (base64String );
372+ this .byteArray = defaultInitializer ();
373+ }
374+
375+ LazyByteArray (@ Nonnull ByteArray byteArray ) {
376+ this .base64String =
377+ ENCODER .encodeToString (Preconditions .checkNotNull (byteArray ).toByteArray ());
378+ this .byteArray =
379+ new AbstractLazyInitializer <ByteArray >() {
380+ @ Override
381+ protected ByteArray initialize () {
382+ return byteArray ;
383+ }
384+ };
385+ }
386+
387+ private AbstractLazyInitializer <ByteArray > defaultInitializer () {
388+ return new AbstractLazyInitializer <ByteArray >() {
389+ @ Override
390+ protected ByteArray initialize () {
391+ return ByteArray .copyFrom (DECODER .decode (base64String ));
392+ }
393+ };
394+ }
395+
396+ private void readObject (java .io .ObjectInputStream in )
397+ throws IOException , ClassNotFoundException {
398+ in .defaultReadObject ();
399+ byteArray = defaultInitializer ();
400+ }
401+
402+ ByteArray getByteArray () {
403+ try {
404+ return byteArray .get ();
405+ } catch (Throwable t ) {
406+ throw SpannerExceptionFactory .asSpannerException (t );
407+ }
408+ }
409+
410+ String getBase64String () {
411+ return base64String ;
412+ }
413+
414+ @ Override
415+ public String toString () {
416+ return getBase64String ();
417+ }
418+
419+ @ Override
420+ public int hashCode () {
421+ return base64String .hashCode ();
422+ }
423+
424+ @ Override
425+ public boolean equals (Object o ) {
426+ if (o instanceof LazyByteArray ) {
427+ return lazyByteArraysEqual ((LazyByteArray ) o );
428+ }
429+ return false ;
430+ }
431+
432+ private boolean lazyByteArraysEqual (LazyByteArray other ) {
433+ return Objects .equals (getBase64String (), other .getBase64String ());
434+ }
435+ }
436+
356437 static class GrpcStruct extends Struct implements Serializable {
357438 private final Type type ;
358439 private final List <Object > rowData ;
@@ -395,7 +476,11 @@ private Object writeReplace() {
395476 builder .set (fieldName ).to (Value .pgJsonb ((String ) value ));
396477 break ;
397478 case BYTES :
398- builder .set (fieldName ).to ((ByteArray ) value );
479+ builder
480+ .set (fieldName )
481+ .to (
482+ Value .bytesFromBase64 (
483+ value == null ? null : ((LazyByteArray ) value ).getBase64String ()));
399484 break ;
400485 case TIMESTAMP :
401486 builder .set (fieldName ).to ((Timestamp ) value );
@@ -431,7 +516,17 @@ private Object writeReplace() {
431516 builder .set (fieldName ).toPgJsonbArray ((Iterable <String >) value );
432517 break ;
433518 case BYTES :
434- builder .set (fieldName ).toBytesArray ((Iterable <ByteArray >) value );
519+ builder
520+ .set (fieldName )
521+ .toBytesArrayFromBase64 (
522+ value == null
523+ ? null
524+ : ((List <LazyByteArray >) value )
525+ .stream ()
526+ .map (
527+ element ->
528+ element == null ? null : element .getBase64String ())
529+ .collect (Collectors .toList ()));
435530 break ;
436531 case TIMESTAMP :
437532 builder .set (fieldName ).toTimestampArray ((Iterable <Timestamp >) value );
@@ -511,7 +606,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
511606 return proto .getStringValue ();
512607 case BYTES :
513608 checkType (fieldType , proto , KindCase .STRING_VALUE );
514- return ByteArray . fromBase64 (proto .getStringValue ());
609+ return new LazyByteArray (proto .getStringValue ());
515610 case TIMESTAMP :
516611 checkType (fieldType , proto , KindCase .STRING_VALUE );
517612 return Timestamp .parseTimestamp (proto .getStringValue ());
@@ -526,6 +621,8 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
526621 checkType (fieldType , proto , KindCase .LIST_VALUE );
527622 ListValue structValue = proto .getListValue ();
528623 return decodeStructValue (fieldType , structValue );
624+ case UNRECOGNIZED :
625+ return proto ;
529626 default :
530627 throw new AssertionError ("Unhandled type code: " + fieldType .getCode ());
531628 }
@@ -634,7 +731,11 @@ protected String getPgJsonbInternal(int columnIndex) {
634731
635732 @ Override
636733 protected ByteArray getBytesInternal (int columnIndex ) {
637- return (ByteArray ) rowData .get (columnIndex );
734+ return getLazyBytesInternal (columnIndex ).getByteArray ();
735+ }
736+
737+ LazyByteArray getLazyBytesInternal (int columnIndex ) {
738+ return (LazyByteArray ) rowData .get (columnIndex );
638739 }
639740
640741 @ Override
@@ -647,6 +748,10 @@ protected Date getDateInternal(int columnIndex) {
647748 return (Date ) rowData .get (columnIndex );
648749 }
649750
751+ protected com .google .protobuf .Value getProtoValueInternal (int columnIndex ) {
752+ return (com .google .protobuf .Value ) rowData .get (columnIndex );
753+ }
754+
650755 @ Override
651756 protected Value getValueInternal (int columnIndex ) {
652757 final List <Type .StructField > structFields = getType ().getStructFields ();
@@ -671,13 +776,16 @@ protected Value getValueInternal(int columnIndex) {
671776 case PG_JSONB :
672777 return Value .pgJsonb (isNull ? null : getPgJsonbInternal (columnIndex ));
673778 case BYTES :
674- return Value .bytes (isNull ? null : getBytesInternal (columnIndex ));
779+ return Value .internalBytes (isNull ? null : getLazyBytesInternal (columnIndex ));
675780 case TIMESTAMP :
676781 return Value .timestamp (isNull ? null : getTimestampInternal (columnIndex ));
677782 case DATE :
678783 return Value .date (isNull ? null : getDateInternal (columnIndex ));
679784 case STRUCT :
680785 return Value .struct (isNull ? null : getStructInternal (columnIndex ));
786+ case UNRECOGNIZED :
787+ return Value .unrecognized (
788+ isNull ? NULL_VALUE : getProtoValueInternal (columnIndex ), columnType );
681789 case ARRAY :
682790 final Type elementType = columnType .getArrayElementType ();
683791 switch (elementType .getCode ()) {
@@ -785,9 +893,10 @@ protected List<String> getPgJsonbListInternal(int columnIndex) {
785893 }
786894
787895 @ Override
788- @ SuppressWarnings ("unchecked" ) // We know ARRAY<BYTES> produces a List<ByteArray >.
896+ @ SuppressWarnings ("unchecked" ) // We know ARRAY<BYTES> produces a List<LazyByteArray >.
789897 protected List <ByteArray > getBytesListInternal (int columnIndex ) {
790- return Collections .unmodifiableList ((List <ByteArray >) rowData .get (columnIndex ));
898+ return Lists .transform (
899+ (List <LazyByteArray >) rowData .get (columnIndex ), l -> l == null ? null : l .getByteArray ());
791900 }
792901
793902 @ Override
0 commit comments