Skip to content

Commit dd73f39

Browse files
committed
feat: add support for lazy bytes arrays
1 parent 066f6dc commit dd73f39

File tree

7 files changed

+231
-86
lines changed

7 files changed

+231
-86
lines changed

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

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
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;
@@ -62,13 +63,16 @@
6263
import java.util.Iterator;
6364
import java.util.LinkedList;
6465
import java.util.List;
66+
import java.util.Objects;
6567
import java.util.concurrent.BlockingQueue;
6668
import java.util.concurrent.CountDownLatch;
6769
import java.util.concurrent.Executor;
6870
import java.util.concurrent.LinkedBlockingQueue;
6971
import java.util.concurrent.TimeUnit;
7072
import java.util.logging.Level;
7173
import java.util.logging.Logger;
74+
import java.util.stream.Collectors;
75+
import javax.annotation.Nonnull;
7276
import javax.annotation.Nullable;
7377

7478
/** Implementation of {@link ResultSet}. */
@@ -357,19 +361,42 @@ private boolean isMergeable(KindCase kind) {
357361
}
358362
}
359363

360-
static class LazyByteArray implements Serializable {
364+
static final class LazyByteArray implements Serializable {
365+
private static final Base64.Encoder ENCODER = Base64.getEncoder();
361366
private static final Base64.Decoder DECODER = Base64.getDecoder();
362367
private final String base64String;
363-
private final transient AbstractLazyInitializer<ByteArray> byteArray =
364-
new AbstractLazyInitializer<ByteArray>() {
365-
@Override
366-
protected ByteArray initialize() {
367-
return ByteArray.copyFrom(DECODER.decode(base64String));
368-
}
369-
};
368+
private transient AbstractLazyInitializer<ByteArray> byteArray;
369+
370+
LazyByteArray(@Nonnull String base64String) {
371+
this.base64String = Preconditions.checkNotNull(base64String);
372+
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+
}
370395

371-
LazyByteArray(String base64String) {
372-
this.base64String = base64String;
396+
private void readObject(java.io.ObjectInputStream in)
397+
throws IOException, ClassNotFoundException {
398+
in.defaultReadObject();
399+
byteArray = defaultInitializer();
373400
}
374401

375402
ByteArray getByteArray() {
@@ -388,6 +415,23 @@ String getBase64String() {
388415
public String toString() {
389416
return getBase64String();
390417
}
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+
}
391435
}
392436

393437
static class GrpcStruct extends Struct implements Serializable {
@@ -435,9 +479,8 @@ private Object writeReplace() {
435479
builder
436480
.set(fieldName)
437481
.to(
438-
value instanceof LazyByteArray
439-
? ((LazyByteArray) value).getByteArray()
440-
: (ByteArray) value);
482+
Value.bytesFromBase64(
483+
value == null ? null : ((LazyByteArray) value).getBase64String()));
441484
break;
442485
case TIMESTAMP:
443486
builder.set(fieldName).to((Timestamp) value);
@@ -473,7 +516,17 @@ private Object writeReplace() {
473516
builder.set(fieldName).toPgJsonbArray((Iterable<String>) value);
474517
break;
475518
case BYTES:
476-
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()));
477530
break;
478531
case TIMESTAMP:
479532
builder.set(fieldName).toTimestampArray((Iterable<Timestamp>) value);
@@ -720,7 +773,7 @@ protected Value getValueInternal(int columnIndex) {
720773
case PG_JSONB:
721774
return Value.pgJsonb(isNull ? null : getPgJsonbInternal(columnIndex));
722775
case BYTES:
723-
return Value.lazyBytes(isNull ? null : getLazyBytesInternal(columnIndex));
776+
return Value.internalBytes(isNull ? null : getLazyBytesInternal(columnIndex));
724777
case TIMESTAMP:
725778
return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex));
726779
case DATE:

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

Lines changed: 64 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.protobuf.ListValue;
3030
import com.google.protobuf.NullValue;
3131
import com.google.protobuf.Value.KindCase;
32+
import java.io.IOException;
3233
import java.io.Serializable;
3334
import java.math.BigDecimal;
3435
import java.util.ArrayList;
@@ -227,16 +228,22 @@ public static Value pgJsonb(@Nullable String v) {
227228
* @param v the value, which may be null
228229
*/
229230
public static Value bytes(@Nullable ByteArray v) {
230-
return new BytesImpl(v == null, v);
231+
return new LazyBytesImpl(v == null, v);
231232
}
232233

233234
/**
234-
* Returns a {@code BYTES} value that is lazily constructed when needed.
235+
* Returns a {@code BYTES} value.
235236
*
236-
* @param v the value, which may be null
237+
* @param base64String the value in Base64 encoding, which may be null. This value must be a valid
238+
* base64 string.
237239
*/
238-
static Value lazyBytes(@Nullable LazyByteArray v) {
239-
return new LazyBytesImpl(v == null, v);
240+
public static Value bytesFromBase64(@Nullable String base64String) {
241+
return new LazyBytesImpl(
242+
base64String == null, base64String == null ? null : new LazyByteArray(base64String));
243+
}
244+
245+
static Value internalBytes(@Nullable LazyByteArray bytes) {
246+
return new LazyBytesImpl(bytes == null, bytes);
240247
}
241248

242249
/** Returns a {@code TIMESTAMP} value. */
@@ -441,11 +448,37 @@ public static Value pgJsonbArray(@Nullable Iterable<String> v) {
441448
* {@code isNull()} is {@code true}. Individual elements may also be {@code null}.
442449
*/
443450
public static Value bytesArray(@Nullable Iterable<ByteArray> v) {
444-
return new BytesArrayImpl(v == null, v == null ? null : immutableCopyOf(v));
451+
return new LazyBytesArrayImpl(v == null, v == null ? null : byteArraysToLazyByteArrayList(v));
445452
}
446453

447-
static Value lazyBytesArray(@Nullable Iterable<LazyByteArray> v) {
448-
return new LazyBytesArrayImpl(v == null, v == null ? null : immutableCopyOf(v));
454+
private static List<LazyByteArray> byteArraysToLazyByteArrayList(Iterable<ByteArray> byteArrays) {
455+
List<LazyByteArray> list = new ArrayList<>();
456+
for (ByteArray byteArray : byteArrays) {
457+
list.add(byteArray == null ? null : new LazyByteArray(byteArray));
458+
}
459+
return Collections.unmodifiableList(list);
460+
}
461+
462+
/**
463+
* Returns an {@code ARRAY<BYTES>} value.
464+
*
465+
* @param base64Strings the source of element values. This may be {@code null} to produce a value
466+
* for which {@code isNull()} is {@code true}. Individual elements may also be {@code null}.
467+
* Non-null values must be a valid Base64 string.
468+
*/
469+
public static Value bytesArrayFromBase64(@Nullable Iterable<String> base64Strings) {
470+
return new LazyBytesArrayImpl(
471+
base64Strings == null,
472+
base64Strings == null ? null : base64StringsToLazyByteArrayList(base64Strings));
473+
}
474+
475+
private static List<LazyByteArray> base64StringsToLazyByteArrayList(
476+
Iterable<String> base64Strings) {
477+
List<LazyByteArray> list = new ArrayList<>();
478+
for (String base64 : base64Strings) {
479+
list.add(base64 == null ? null : new LazyByteArray(base64));
480+
}
481+
return Collections.unmodifiableList(list);
449482
}
450483

451484
/**
@@ -1400,41 +1433,16 @@ void valueToString(StringBuilder b) {
14001433
}
14011434
}
14021435

1403-
private static class BytesImpl extends AbstractObjectValue<ByteArray> {
1404-
1405-
private BytesImpl(boolean isNull, ByteArray value) {
1406-
super(isNull, Type.bytes(), value);
1407-
}
1408-
1409-
@Override
1410-
public ByteArray getBytes() {
1411-
checkNotNull();
1412-
return value;
1413-
}
1414-
1415-
@Override
1416-
com.google.protobuf.Value valueToProto() {
1417-
return com.google.protobuf.Value.newBuilder().setStringValue(value.toBase64()).build();
1418-
}
1419-
1420-
@Nonnull
1421-
@Override
1422-
public String getAsString() {
1423-
return value == null ? NULL_STRING : value.toBase64();
1424-
}
1425-
1426-
@Override
1427-
void valueToString(StringBuilder b) {
1428-
b.append(value.toString());
1429-
}
1430-
}
1431-
14321436
private static class LazyBytesImpl extends AbstractObjectValue<LazyByteArray> {
14331437

14341438
private LazyBytesImpl(boolean isNull, LazyByteArray value) {
14351439
super(isNull, Type.bytes(), value);
14361440
}
14371441

1442+
private LazyBytesImpl(boolean isNull, ByteArray value) {
1443+
super(isNull, Type.bytes(), value == null ? null : new LazyByteArray(value));
1444+
}
1445+
14381446
@Override
14391447
public ByteArray getBytes() {
14401448
checkNotNull();
@@ -1449,12 +1457,12 @@ com.google.protobuf.Value valueToProto() {
14491457
@Nonnull
14501458
@Override
14511459
public String getAsString() {
1452-
return value.getBase64String();
1460+
return value == null ? NULL_STRING : value.getBase64String();
14531461
}
14541462

14551463
@Override
14561464
void valueToString(StringBuilder b) {
1457-
b.append(value.toString());
1465+
b.append(value == null ? null : value.toString());
14581466
}
14591467
}
14601468

@@ -1906,39 +1914,28 @@ void appendElement(StringBuilder b, String element) {
19061914
}
19071915
}
19081916

1909-
private static class BytesArrayImpl extends AbstractArrayValue<ByteArray> {
1910-
private BytesArrayImpl(boolean isNull, @Nullable List<ByteArray> values) {
1911-
super(isNull, Type.bytes(), values);
1912-
}
1913-
1914-
@Override
1915-
public List<ByteArray> getBytesArray() {
1916-
checkNotNull();
1917-
return value;
1918-
}
1917+
private static class LazyBytesArrayImpl extends AbstractArrayValue<LazyByteArray> {
1918+
private transient AbstractLazyInitializer<List<ByteArray>> bytesArray = defaultInitializer();
19191919

1920-
@Override
1921-
String elementToString(ByteArray element) {
1922-
return element.toBase64();
1920+
private LazyBytesArrayImpl(boolean isNull, @Nullable List<LazyByteArray> values) {
1921+
super(isNull, Type.bytes(), values);
19231922
}
19241923

1925-
@Override
1926-
void appendElement(StringBuilder b, ByteArray element) {
1927-
b.append(elementToString(element));
1924+
private AbstractLazyInitializer<List<ByteArray>> defaultInitializer() {
1925+
return new AbstractLazyInitializer<List<ByteArray>>() {
1926+
@Override
1927+
protected List<ByteArray> initialize() {
1928+
return value.stream()
1929+
.map(element -> element == null ? null : element.getByteArray())
1930+
.collect(Collectors.toList());
1931+
}
1932+
};
19281933
}
1929-
}
1930-
1931-
private static class LazyBytesArrayImpl extends AbstractArrayValue<LazyByteArray> {
1932-
private final AbstractLazyInitializer<List<ByteArray>> bytesArray =
1933-
new AbstractLazyInitializer<List<ByteArray>>() {
1934-
@Override
1935-
protected List<ByteArray> initialize() {
1936-
return value.stream().map(LazyByteArray::getByteArray).collect(Collectors.toList());
1937-
}
1938-
};
19391934

1940-
private LazyBytesArrayImpl(boolean isNull, @Nullable List<LazyByteArray> values) {
1941-
super(isNull, Type.bytes(), values);
1935+
private void readObject(java.io.ObjectInputStream in)
1936+
throws IOException, ClassNotFoundException {
1937+
in.defaultReadObject();
1938+
bytesArray = defaultInitializer();
19421939
}
19431940

19441941
@Override

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ public R to(@Nullable String value) {
9595
return handle(Value.string(value));
9696
}
9797

98-
/** Binds to {@code Value.bytes(value)} */
98+
/**
99+
* Binds to {@code Value.bytes(value)}. Use {@link #to(Value)} in combination with {@link
100+
* Value#bytes(String)} if you already have the value that you want to bind in base64 format. This
101+
* prevents unnecessary decoding and encoding of base64 strings.
102+
*/
99103
public R to(@Nullable ByteArray value) {
100104
return handle(Value.bytes(value));
101105
}
@@ -198,6 +202,15 @@ public R toBytesArray(@Nullable Iterable<ByteArray> values) {
198202
return handle(Value.bytesArray(values));
199203
}
200204

205+
/**
206+
* Binds to {@code Value.bytesArray(values)}. The given strings must be valid base64 encoded
207+
* strings. Use this method instead of {@link #toBytesArray(Iterable)} if you already have the
208+
* values in base64 format to prevent unnecessary decoding and encoding to/from base64.
209+
*/
210+
public R toBytesArrayFromBase64(@Nullable Iterable<String> valuesAsBase64Strings) {
211+
return handle(Value.bytesArrayFromBase64(valuesAsBase64Strings));
212+
}
213+
201214
/** Binds to {@code Value.timestampArray(values)} */
202215
public R toTimestampArray(@Nullable Iterable<Timestamp> values) {
203216
return handle(Value.timestampArray(values));

0 commit comments

Comments
 (0)