list = new ArrayList<>();
+ for (String base64 : base64Strings) {
+ list.add(base64 == null ? null : new LazyByteArray(base64));
+ }
+ return Collections.unmodifiableList(list);
}
/**
@@ -672,12 +731,41 @@ public String toString() {
return b.toString();
}
+ /**
+ * Returns this value as a raw string representation. This is guaranteed to work for all values,
+ * regardless of the underlying data type, and is guaranteed not to be truncated.
+ *
+ * Returns the string "NULL" for null values.
+ */
+ @Nonnull
+ public String getAsString() {
+ return toString();
+ }
+
+ /**
+ * Returns this value as a list of raw string representations. This is guaranteed to work for all
+ * values, regardless of the underlying data type, and the strings are guaranteed not to be
+ * truncated. The method returns a singleton list for non-array values and a list containing as
+ * many elements as there are in the array for array values. This method can be used instead of
+ * the {@link #getAsString()} method if you need to quote the individual elements in an array.
+ *
+ *
Returns the string "NULL" for null values.
+ */
+ @Nonnull
+ public ImmutableList getAsStringList() {
+ return ImmutableList.of(toString());
+ }
+
// END OF PUBLIC API.
static com.google.protobuf.Value toProto(Value value) {
return value == null ? NULL_PROTO : value.toProto();
}
+ /**
+ * Appends a string representation of this value to the given builder. The string representation
+ * can be truncated.
+ */
abstract void toString(StringBuilder b);
abstract com.google.protobuf.Value toProto();
@@ -948,7 +1036,7 @@ final void toString(StringBuilder b) {
/**
* Appends a representation of {@code this} to {@code b}. {@code this} is guaranteed to
- * represent a non-null value.
+ * represent a non-null value. This value could be truncated if the underlying value is long.
*/
abstract void valueToString(StringBuilder b);
@@ -1022,11 +1110,16 @@ final void checkNotNull() {
}
}
- private static class UntypedValueImpl extends AbstractValue {
+ /**
+ * This {@link Value} implementation is backed by a generic protobuf Value instance. It is used
+ * for untyped Values that are created by users, and for values with an unrecognized types that
+ * coming from the backend.
+ */
+ private static class ProtoBackedValueImpl extends AbstractValue {
private final com.google.protobuf.Value value;
- private UntypedValueImpl(com.google.protobuf.Value value) {
- super(value.hasNullValue(), null);
+ private ProtoBackedValueImpl(com.google.protobuf.Value value, @Nullable Type type) {
+ super(value.hasNullValue(), type);
this.value = value;
}
@@ -1053,6 +1146,44 @@ public double getFloat64() {
return value.getNumberValue();
}
+ @Nonnull
+ @Override
+ public String getAsString() {
+ switch (value.getKindCase()) {
+ case NULL_VALUE:
+ return NULL_STRING;
+ case NUMBER_VALUE:
+ return Double.toString(value.getNumberValue());
+ case STRING_VALUE:
+ return value.getStringValue();
+ case BOOL_VALUE:
+ return Boolean.toString(value.getBoolValue());
+ case LIST_VALUE:
+ return value.getListValue().getValuesList().stream()
+ .map(element -> Value.untyped(element).getAsString())
+ .collect(Collectors.joining(",", "[", "]"));
+ case STRUCT_VALUE:
+ throw new IllegalArgumentException(
+ "Struct value with unrecognized type is not supported");
+ case KIND_NOT_SET:
+ default:
+ throw new IllegalArgumentException("Kind of value is not set or unknown");
+ }
+ }
+
+ @Nonnull
+ @Override
+ public ImmutableList getAsStringList() {
+ if (value.getKindCase() == KindCase.LIST_VALUE) {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ value.getListValue().getValuesList().stream()
+ .map(v -> Value.untyped(v).getAsString())
+ .forEach(builder::add);
+ return builder.build();
+ }
+ return ImmutableList.of(getAsString());
+ }
+
@Override
void valueToString(StringBuilder b) {
b.append(value);
@@ -1065,7 +1196,7 @@ com.google.protobuf.Value valueToProto() {
@Override
boolean valueEquals(Value v) {
- return ((UntypedValueImpl) v).value.equals(value);
+ return ((ProtoBackedValueImpl) v).value.equals(value);
}
@Override
@@ -1234,6 +1365,12 @@ public String getString() {
return value;
}
+ @Nonnull
+ @Override
+ public String getAsString() {
+ return isNull() ? NULL_STRING : value;
+ }
+
@Override
void valueToString(StringBuilder b) {
if (value.length() > MAX_DEBUG_STRING_LENGTH) {
@@ -1256,6 +1393,12 @@ public String getJson() {
return value;
}
+ @Nonnull
+ @Override
+ public String getAsString() {
+ return isNull() ? NULL_STRING : value;
+ }
+
@Override
public String getString() {
return getJson();
@@ -1283,6 +1426,12 @@ public String getPgJsonb() {
return value;
}
+ @Nonnull
+ @Override
+ public String getAsString() {
+ return isNull() ? NULL_STRING : value;
+ }
+
@Override
public String getString() {
return getPgJsonb();
@@ -1298,26 +1447,36 @@ void valueToString(StringBuilder b) {
}
}
- private static class BytesImpl extends AbstractObjectValue {
+ private static class LazyBytesImpl extends AbstractObjectValue {
- private BytesImpl(boolean isNull, ByteArray value) {
+ private LazyBytesImpl(boolean isNull, LazyByteArray value) {
super(isNull, Type.bytes(), value);
}
+ private LazyBytesImpl(boolean isNull, ByteArray value) {
+ super(isNull, Type.bytes(), value == null ? null : new LazyByteArray(value));
+ }
+
@Override
public ByteArray getBytes() {
checkNotNull();
- return value;
+ return value.getByteArray();
}
@Override
com.google.protobuf.Value valueToProto() {
- return com.google.protobuf.Value.newBuilder().setStringValue(value.toBase64()).build();
+ return com.google.protobuf.Value.newBuilder().setStringValue(value.getBase64String()).build();
+ }
+
+ @Nonnull
+ @Override
+ public String getAsString() {
+ return value == null ? NULL_STRING : value.getBase64String();
}
@Override
void valueToString(StringBuilder b) {
- b.append(value.toString());
+ b.append(value == null ? null : value.toString());
}
}
@@ -1483,6 +1642,16 @@ List getArray() {
abstract com.google.protobuf.Value getValueAsProto(int i);
+ @Nonnull
+ @Override
+ public ImmutableList getAsStringList() {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (int i = 0; i < size(); i++) {
+ builder.add(isElementNull(i) ? NULL_STRING : String.valueOf(getValue(i)));
+ }
+ return builder.build();
+ }
+
@Override
void valueToString(StringBuilder b) {
b.append(LIST_OPEN);
@@ -1661,6 +1830,16 @@ com.google.protobuf.Value valueToProto() {
return com.google.protobuf.Value.newBuilder().setListValue(list).build();
}
+ @Nonnull
+ @Override
+ public ImmutableList getAsStringList() {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (T element : value) {
+ builder.add(element == null ? NULL_STRING : elementToString(element));
+ }
+ return builder.build();
+ }
+
String elementToString(T element) {
return element.toString();
}
@@ -1749,25 +1928,48 @@ void appendElement(StringBuilder b, String element) {
}
}
- private static class BytesArrayImpl extends AbstractArrayValue {
- private BytesArrayImpl(boolean isNull, @Nullable List values) {
+ private static class LazyBytesArrayImpl extends AbstractArrayValue {
+ private transient AbstractLazyInitializer> bytesArray = defaultInitializer();
+
+ private LazyBytesArrayImpl(boolean isNull, @Nullable List values) {
super(isNull, Type.bytes(), values);
}
+ private AbstractLazyInitializer> defaultInitializer() {
+ return new AbstractLazyInitializer>() {
+ @Override
+ protected List initialize() {
+ return value.stream()
+ .map(element -> element == null ? null : element.getByteArray())
+ .collect(Collectors.toList());
+ }
+ };
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ bytesArray = defaultInitializer();
+ }
+
@Override
public List getBytesArray() {
checkNotNull();
- return value;
+ try {
+ return bytesArray.get();
+ } catch (Exception e) {
+ throw SpannerExceptionFactory.asSpannerException(e);
+ }
}
@Override
- String elementToString(ByteArray element) {
- return element.toBase64();
+ String elementToString(LazyByteArray element) {
+ return element.getBase64String();
}
@Override
- void appendElement(StringBuilder b, ByteArray element) {
- b.append(element.toString());
+ void appendElement(StringBuilder b, LazyByteArray element) {
+ b.append(elementToString(element));
}
}
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..e16b858faa8 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
@@ -95,7 +95,11 @@ public R to(@Nullable String value) {
return handle(Value.string(value));
}
- /** Binds to {@code Value.bytes(value)} */
+ /**
+ * Binds to {@code Value.bytes(value)}. Use {@link #to(Value)} in combination with {@link
+ * Value#bytesFromBase64(String)} if you already have the value that you want to bind in base64
+ * format. This prevents unnecessary decoding and encoding of base64 strings.
+ */
public R to(@Nullable ByteArray value) {
return handle(Value.bytes(value));
}
@@ -198,6 +202,15 @@ public R toBytesArray(@Nullable Iterable values) {
return handle(Value.bytesArray(values));
}
+ /**
+ * Binds to {@code Value.bytesArray(values)}. The given strings must be valid base64 encoded
+ * strings. Use this method instead of {@link #toBytesArray(Iterable)} if you already have the
+ * values in base64 format to prevent unnecessary decoding and encoding to/from base64.
+ */
+ public R toBytesArrayFromBase64(@Nullable Iterable valuesAsBase64Strings) {
+ return handle(Value.bytesArrayFromBase64(valuesAsBase64Strings));
+ }
+
/** Binds to {@code Value.timestampArray(values)} */
public R toTimestampArray(@Nullable Iterable values) {
return handle(Value.timestampArray(values));
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
index 666152eeca4..ac0f0a98d29 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
@@ -37,6 +37,7 @@
import com.google.api.core.ApiFutures;
import com.google.api.gax.grpc.testing.LocalChannelProvider;
import com.google.api.gax.retrying.RetrySettings;
+import com.google.cloud.ByteArray;
import com.google.cloud.NoCredentials;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.AbstractResultSet.GrpcStreamIterator;
@@ -50,10 +51,15 @@
import com.google.cloud.spanner.SessionPool.PooledSessionFuture;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.cloud.spanner.SpannerOptions.SpannerCallContextTimeoutConfigurator;
+import com.google.cloud.spanner.Type.Code;
+import com.google.cloud.spanner.connection.RandomResultSetGenerator;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
+import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.AbstractMessage;
+import com.google.protobuf.ListValue;
+import com.google.protobuf.NullValue;
import com.google.spanner.v1.CommitRequest;
import com.google.spanner.v1.DeleteSessionRequest;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
@@ -67,6 +73,7 @@
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
import com.google.spanner.v1.Type;
+import com.google.spanner.v1.TypeAnnotationCode;
import com.google.spanner.v1.TypeCode;
import io.grpc.Context;
import io.grpc.Server;
@@ -74,9 +81,12 @@
import io.grpc.StatusRuntimeException;
import io.grpc.inprocess.InProcessServerBuilder;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -87,6 +97,7 @@
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -1661,7 +1672,8 @@ public void testBackendPartitionQueryOptions() {
@Test
public void testAsyncQuery() throws Exception {
final int EXPECTED_ROW_COUNT = 10;
- RandomResultSetGenerator generator = new RandomResultSetGenerator(EXPECTED_ROW_COUNT);
+ com.google.cloud.spanner.connection.RandomResultSetGenerator generator =
+ new RandomResultSetGenerator(EXPECTED_ROW_COUNT);
com.google.spanner.v1.ResultSet resultSet = generator.generate();
mockSpanner.putStatementResult(
StatementResult.query(Statement.of("SELECT * FROM RANDOM"), resultSet));
@@ -2381,4 +2393,565 @@ public void testAnalyzeUpdateStatement() {
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
assertEquals(QueryMode.PLAN, request.getQueryMode());
}
+
+ @Test
+ public void testByteArray() {
+ Random random = new Random();
+ byte[] bytes = new byte[random.nextInt(200)];
+ int numRows = 5;
+ List rows = new ArrayList<>(numRows);
+ for (int i = 0; i < numRows; i++) {
+ random.nextBytes(bytes);
+ rows.add(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(
+ // Use both the Guava and the JDK encoder to encode the values to ensure
+ // that encoding/decoding using both of them works.
+ i % 2 == 0
+ ? Base64.getEncoder().encodeToString(bytes)
+ : BaseEncoding.base64().encode(bytes))
+ .build())
+ .build());
+ }
+ Statement statement = Statement.of("select * from foo");
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ statement,
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setType(Type.newBuilder().setCode(TypeCode.BYTES).build())
+ .setName("f1")
+ .build())
+ .build())
+ .build())
+ .addAllRows(rows)
+ .build()));
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ String base64String = resultSet.getValue(0).getAsString();
+ ByteArray byteArray = resultSet.getBytes(0);
+ // Use the 'old' ByteArray.fromBase64(..) method that uses the Guava encoder to ensure that
+ // the two encoders (JDK and Guava) return the same values.
+ assertEquals(ByteArray.fromBase64(base64String), byteArray);
+ }
+ }
+ }
+
+ @Test
+ public void testGetAllTypesAsString() {
+ for (Dialect dialect : Dialect.values()) {
+ Statement statement = Statement.of("select * from all_types");
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ statement,
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ RandomResultSetGenerator.generateAllTypesMetadata(
+ RandomResultSetGenerator.generateAllTypes(dialect)))
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setBoolValue(true).build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setStringValue("100").build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("6.626")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("test-string")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("{\"key1\": \"value1\"}")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(
+ Base64.getEncoder()
+ .encodeToString(
+ "test-bytes".getBytes(StandardCharsets.UTF_8)))
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2023-01-11")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2023-01-11T11:55:18.123456789Z")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setBoolValue(true)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setBoolValue(false)
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(String.valueOf(Long.MAX_VALUE))
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(String.valueOf(Long.MIN_VALUE))
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNumberValue(-12345.6789d)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNumberValue(3.14d)
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("6.626")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("-8.9123")
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("test-string1")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("test-string2")
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("{\"key\": \"value1\"}")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("{\"key\": \"value2\"}")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(
+ Base64.getEncoder()
+ .encodeToString(
+ "test-bytes1"
+ .getBytes(
+ StandardCharsets.UTF_8)))
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue(
+ Base64.getEncoder()
+ .encodeToString(
+ "test-bytes2"
+ .getBytes(
+ StandardCharsets.UTF_8)))
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2000-02-29")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2000-01-01")
+ .build())
+ .build()))
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2023-01-11T11:55:18.123456789Z")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("2023-01-12T11:55:18Z")
+ .build())
+ .build()))
+ .build())
+ .build()));
+
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ int col = 0;
+ assertAsString("true", resultSet, col++);
+ assertAsString("100", resultSet, col++);
+ assertAsString("3.14", resultSet, col++);
+ assertAsString("6.626", resultSet, col++);
+ assertAsString("test-string", resultSet, col++);
+ assertAsString("{\"key1\": \"value1\"}", resultSet, col++);
+ assertAsString(
+ Base64.getEncoder().encodeToString("test-bytes".getBytes(StandardCharsets.UTF_8)),
+ resultSet,
+ col++);
+ assertAsString("2023-01-11", resultSet, col++);
+ assertAsString("2023-01-11T11:55:18.123456789Z", resultSet, col++);
+
+ assertAsString(ImmutableList.of("true", "NULL", "false"), resultSet, col++);
+ assertAsString(
+ ImmutableList.of(
+ String.format("%d", Long.MAX_VALUE), String.format("%d", Long.MIN_VALUE), "NULL"),
+ resultSet,
+ col++);
+ assertAsString(ImmutableList.of("NULL", "-12345.6789", "3.14"), resultSet, col++);
+ assertAsString(ImmutableList.of("6.626", "NULL", "-8.9123"), resultSet, col++);
+ assertAsString(ImmutableList.of("test-string1", "NULL", "test-string2"), resultSet, col++);
+ assertAsString(
+ ImmutableList.of("{\"key\": \"value1\"}", "{\"key\": \"value2\"}", "NULL"),
+ resultSet,
+ col++);
+ assertAsString(
+ ImmutableList.of(
+ String.format(
+ "%s",
+ Base64.getEncoder()
+ .encodeToString("test-bytes1".getBytes(StandardCharsets.UTF_8))),
+ String.format(
+ "%s",
+ Base64.getEncoder()
+ .encodeToString("test-bytes2".getBytes(StandardCharsets.UTF_8))),
+ "NULL"),
+ resultSet,
+ col++);
+ assertAsString(ImmutableList.of("2000-02-29", "NULL", "2000-01-01"), resultSet, col++);
+ assertAsString(
+ ImmutableList.of("2023-01-11T11:55:18.123456789Z", "NULL", "2023-01-12T11:55:18Z"),
+ resultSet,
+ col++);
+
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ public void testSelectUnknownType() {
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of("SELECT * FROM foo"),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("c")
+ .setType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setStringValue("bar").build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setBoolValue(true).build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setListValue(
+ ListValue.newBuilder()
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setStringValue("baz")
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setBoolValue(false)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNumberValue(6.626)
+ .build())
+ .addValues(
+ com.google.protobuf.Value.newBuilder()
+ .setNullValue(NullValue.NULL_VALUE)
+ .build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ try (ResultSet resultSet = client.singleUse().executeQuery(Statement.of("SELECT * FROM foo"))) {
+ assertTrue(resultSet.next());
+ assertAsString("bar", resultSet, 0);
+
+ assertTrue(resultSet.next());
+ assertAsString("true", resultSet, 0);
+
+ assertTrue(resultSet.next());
+ assertAsString("3.14", resultSet, 0);
+
+ assertTrue(resultSet.next());
+ assertAsString("NULL", resultSet, 0);
+
+ assertTrue(resultSet.next());
+ assertAsString(ImmutableList.of("baz", "false", "6.626", "NULL"), resultSet, 0);
+
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testMetadataUnknownTypes() {
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of("SELECT * FROM foo"),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("c1")
+ .setType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c2")
+ .setType(
+ Type.newBuilder()
+ .setCode(TypeCode.STRING)
+ .setTypeAnnotationValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c3")
+ .setType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c4")
+ .setType(
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c5")
+ .setType(
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ Type.newBuilder()
+ .setCode(TypeCode.STRING)
+ .setTypeAnnotationValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c6")
+ .setType(
+ Type.newBuilder()
+ // Set an unrecognized type with an array element
+ // type. The client should recognize this as an
+ // array.
+ .setCodeValue(Integer.MAX_VALUE)
+ .setArrayElementType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .build())
+ .build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("c7")
+ .setType(
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ Type.newBuilder()
+ .setCodeValue(Integer.MAX_VALUE)
+ .setTypeAnnotation(
+ TypeAnnotationCode.PG_NUMERIC)
+ .build())
+ .build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ try (ResultSet resultSet = client.singleUse().executeQuery(Statement.of("SELECT * FROM foo"))) {
+ // There are no rows, but we need to call resultSet.next() before we can get the metadata.
+ assertFalse(resultSet.next());
+ assertEquals(
+ "STRUCT, c3 UNRECOGNIZED, c4 ARRAY, c5 ARRAY>, c6 UNRECOGNIZED, c7 ARRAY>>",
+ resultSet.getType().toString());
+ assertEquals(
+ "UNRECOGNIZED", resultSet.getType().getStructFields().get(0).getType().toString());
+ assertEquals(
+ "STRING",
+ resultSet.getType().getStructFields().get(1).getType().toString());
+ assertEquals(
+ "UNRECOGNIZED",
+ resultSet.getType().getStructFields().get(2).getType().toString());
+ assertEquals(
+ "ARRAY", resultSet.getType().getStructFields().get(3).getType().toString());
+ assertEquals(Code.ARRAY, resultSet.getType().getStructFields().get(3).getType().getCode());
+ assertEquals(
+ Code.UNRECOGNIZED,
+ resultSet.getType().getStructFields().get(3).getType().getArrayElementType().getCode());
+ assertEquals(
+ "ARRAY>",
+ resultSet.getType().getStructFields().get(4).getType().toString());
+ assertEquals(Code.ARRAY, resultSet.getType().getStructFields().get(4).getType().getCode());
+ assertEquals(
+ Code.UNRECOGNIZED,
+ resultSet.getType().getStructFields().get(4).getType().getArrayElementType().getCode());
+ assertEquals(
+ "UNRECOGNIZED",
+ resultSet.getType().getStructFields().get(5).getType().toString());
+ assertEquals(
+ Code.UNRECOGNIZED, resultSet.getType().getStructFields().get(5).getType().getCode());
+ assertEquals(
+ Code.UNRECOGNIZED,
+ resultSet.getType().getStructFields().get(5).getType().getArrayElementType().getCode());
+ assertEquals(
+ "ARRAY>",
+ resultSet.getType().getStructFields().get(6).getType().toString());
+ assertEquals(Code.ARRAY, resultSet.getType().getStructFields().get(6).getType().getCode());
+ assertEquals(
+ Code.UNRECOGNIZED,
+ resultSet.getType().getStructFields().get(6).getType().getArrayElementType().getCode());
+ }
+ }
+
+ static void assertAsString(String expected, ResultSet resultSet, int col) {
+ assertEquals(expected, resultSet.getValue(col).getAsString());
+ assertEquals(ImmutableList.of(expected), resultSet.getValue(col).getAsStringList());
+ }
+
+ static void assertAsString(ImmutableList expected, ResultSet resultSet, int col) {
+ assertEquals(expected, resultSet.getValue(col).getAsStringList());
+ assertEquals(
+ expected.stream().collect(Collectors.joining(",", "[", "]")),
+ resultSet.getValue(col).getAsString());
+ }
}
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..490eff3aca8 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
@@ -38,8 +38,10 @@
import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.Transaction;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -517,7 +519,10 @@ public void serialization() {
Value.float64(1.0),
Value.float64(null),
Value.bytes(ByteArray.fromBase64("abcd")),
+ Value.bytesFromBase64(
+ Base64.getEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8))),
Value.bytes(null),
+ Value.bytesFromBase64(null),
Value.timestamp(Timestamp.ofTimeSecondsAndNanos(1, 2)),
Value.timestamp(null),
Value.date(Date.fromYearMonthDay(2017, 4, 17)),
@@ -528,6 +533,14 @@ public void serialization() {
Value.boolArray((boolean[]) null),
Value.int64Array(new long[] {1, 2, 3}),
Value.int64Array((long[]) null),
+ Value.float64Array(new double[] {1.1, 2.2, 3.3}),
+ Value.float64Array((double[]) null),
+ Value.bytesArray(Arrays.asList(ByteArray.fromBase64("abcd"), null)),
+ Value.bytesArrayFromBase64(
+ Arrays.asList(
+ Base64.getEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)), null)),
+ Value.bytesArray(null),
+ Value.bytesArrayFromBase64(null),
Value.timestampArray(ImmutableList.of(Timestamp.MAX_VALUE, Timestamp.MAX_VALUE)),
Value.timestampArray(null),
Value.dateArray(
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/LazyByteArrayTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/LazyByteArrayTest.java
new file mode 100644
index 00000000000..a36b3463439
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/LazyByteArrayTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package com.google.cloud.spanner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.google.cloud.spanner.AbstractResultSet.LazyByteArray;
+import java.util.Base64;
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LazyByteArrayTest {
+
+ @Test
+ public void testEqualsAndHashCode() {
+ Random random = new Random();
+ byte[] bytes1 = new byte[random.nextInt(300) + 300];
+ // Make sure the second byte array has a different length than the first to be absolutely sure
+ // that they can never contain the same value.
+ byte[] bytes2 = new byte[bytes1.length + 1];
+ random.nextBytes(bytes1);
+ random.nextBytes(bytes2);
+
+ LazyByteArray lazyByteArray1 = new LazyByteArray(Base64.getEncoder().encodeToString(bytes1));
+ LazyByteArray lazyByteArray2 = new LazyByteArray(Base64.getEncoder().encodeToString(bytes2));
+ LazyByteArray lazyByteArray3 = new LazyByteArray(Base64.getEncoder().encodeToString(bytes1));
+
+ assertEquals(lazyByteArray1, lazyByteArray3);
+ assertNotEquals(lazyByteArray1, lazyByteArray2);
+ assertNotEquals(lazyByteArray2, lazyByteArray3);
+
+ assertEquals(lazyByteArray1.hashCode(), lazyByteArray3.hashCode());
+ }
+}
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..eb17a7a8c62 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
@@ -19,6 +19,8 @@
import static com.google.cloud.spanner.Type.StructField;
import static com.google.common.testing.SerializableTester.reserializeAndAssert;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
@@ -428,9 +430,7 @@ public void structFieldIndexAmbiguous() {
@Test
public void parseErrorMissingTypeCode() {
com.google.spanner.v1.Type proto = com.google.spanner.v1.Type.newBuilder().build();
- IllegalArgumentException e =
- assertThrows(IllegalArgumentException.class, () -> Type.fromProto(proto));
- assertNotNull(e.getMessage());
+ assertEquals(Code.UNRECOGNIZED, Type.fromProto(proto).getCode());
}
@Test
@@ -442,6 +442,81 @@ public void parseErrorMissingArrayElementTypeProto() {
assertNotNull(e.getMessage());
}
+ @Test
+ public void testUnrecognized() {
+ Type unrecognized = Type.fromProto(com.google.spanner.v1.Type.newBuilder().build());
+ assertEquals("TYPE_CODE_UNSPECIFIED", unrecognized.toString());
+ assertEquals(unrecognized, Type.fromProto(com.google.spanner.v1.Type.newBuilder().build()));
+ assertNotEquals(unrecognized, Type.int64());
+ }
+
+ @Test
+ public void testUnrecognizedWithAnnotation() {
+ Type unrecognized =
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build());
+ assertEquals("TYPE_CODE_UNSPECIFIED", unrecognized.toString());
+ assertEquals(
+ unrecognized,
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build()));
+ assertNotEquals(
+ unrecognized,
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setTypeAnnotation(TypeAnnotationCode.PG_JSONB)
+ .build()));
+ assertNotEquals(unrecognized, Type.int64());
+ }
+
+ @Test
+ public void testUnrecognizedArray() {
+ Type unrecognizedArray =
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(com.google.spanner.v1.Type.newBuilder().build())
+ .build());
+ assertEquals("ARRAY", unrecognizedArray.toString());
+ assertEquals(
+ unrecognizedArray,
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(com.google.spanner.v1.Type.newBuilder().build())
+ .build()));
+ assertNotEquals(unrecognizedArray, Type.array(Type.int64()));
+ }
+
+ @Test
+ public void testUnrecognizedArrayWithAnnotation() {
+ Type unrecognizedArray =
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ com.google.spanner.v1.Type.newBuilder()
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build())
+ .build());
+ assertEquals("ARRAY>", unrecognizedArray.toString());
+ assertEquals(
+ unrecognizedArray,
+ Type.fromProto(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ com.google.spanner.v1.Type.newBuilder()
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build())
+ .build()));
+ assertNotEquals(unrecognizedArray, Type.array(Type.int64()));
+ }
+
private static void assertProtoEquals(com.google.spanner.v1.Type proto, String expected) {
MatcherAssert.assertThat(
proto, SpannerMatchers.matchesProto(com.google.spanner.v1.Type.class, expected));
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..8da5c0322dc 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
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultBytesBase64;
import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultJson;
import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultPgJsonb;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +29,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +43,7 @@ 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 BYTES_BASE64_METHOD_NAME = "bytesFromBase64";
public static final String DEFAULT_PG_NUMERIC = "1.23";
private Value lastValue;
@@ -134,6 +138,10 @@ public void reflection()
binderMethod = ValueBinder.class.getMethod("to", Value.class);
assertThat(binderMethod.invoke(binder, Value.pgNumeric(null)))
.isEqualTo(lastReturnValue);
+ } else if (method.getName().equalsIgnoreCase(BYTES_BASE64_METHOD_NAME)) {
+ binderMethod = ValueBinder.class.getMethod("to", Value.class);
+ assertThat(binderMethod.invoke(binder, Value.bytesFromBase64(null)))
+ .isEqualTo(lastReturnValue);
} else {
assertThat(binderMethod.invoke(binder, (Object) null)).isEqualTo(lastReturnValue);
}
@@ -160,6 +168,11 @@ public void reflection()
binderMethod = ValueBinder.class.getMethod("to", Value.class);
assertThat(binderMethod.invoke(binder, Value.pgNumeric(DEFAULT_PG_NUMERIC)))
.isEqualTo(lastReturnValue);
+ } else if (method.getName().equalsIgnoreCase(BYTES_BASE64_METHOD_NAME)) {
+ defaultObject = defaultBytesBase64();
+ binderMethod = ValueBinder.class.getMethod("to", Value.class);
+ assertThat(binderMethod.invoke(binder, Value.bytesFromBase64(defaultBytesBase64())))
+ .isEqualTo(lastReturnValue);
} else {
defaultObject = DefaultValues.getDefault(method.getGenericParameterTypes()[0]);
assertThat(binderMethod.invoke(binder, defaultObject)).isEqualTo(lastReturnValue);
@@ -246,6 +259,10 @@ public static String defaultPgJsonb() {
return "{\"color\":\"red\",\"value\":\"#f00\"}";
}
+ public static String defaultBytesBase64() {
+ return Base64.getEncoder().encodeToString("test-bytes".getBytes(StandardCharsets.UTF_8));
+ }
+
public static ByteArray defaultByteArray() {
return ByteArray.copyFrom(new byte[] {'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..bf806795fa6 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,7 @@
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.AbstractResultSet.LazyByteArray;
import com.google.cloud.spanner.Type.StructField;
import com.google.common.base.Strings;
import com.google.common.collect.ForwardingList;
@@ -41,11 +42,15 @@
import com.google.protobuf.NullValue;
import java.io.Serializable;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Random;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -91,6 +96,8 @@ public void untyped() {
assertNotEquals(
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(false).build()),
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()));
+
+ assertEquals("test", v.getAsString());
}
@Test
@@ -100,6 +107,7 @@ public void bool() {
assertThat(v.isNull()).isFalse();
assertThat(v.getBool()).isTrue();
assertThat(v.toString()).isEqualTo("true");
+ assertEquals("true", v.getAsString());
}
@Test
@@ -109,6 +117,7 @@ public void boolWrapper() {
assertThat(v.isNull()).isFalse();
assertThat(v.getBool()).isFalse();
assertThat(v.toString()).isEqualTo("false");
+ assertEquals("false", v.getAsString());
}
@Test
@@ -119,6 +128,7 @@ public void boolWrapperNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getBool);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -128,6 +138,7 @@ public void int64() {
assertThat(v.isNull()).isFalse();
assertThat(v.getInt64()).isEqualTo(123);
assertThat(v.toString()).isEqualTo("123");
+ assertEquals("123", v.getAsString());
}
@Test
@@ -158,6 +169,7 @@ public void int64Wrapper() {
assertThat(v.isNull()).isFalse();
assertThat(v.getInt64()).isEqualTo(123);
assertThat(v.toString()).isEqualTo("123");
+ assertEquals("123", v.getAsString());
}
@Test
@@ -168,6 +180,7 @@ public void int64WrapperNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -177,6 +190,7 @@ public void float64() {
assertThat(v.isNull()).isFalse();
assertThat(v.getFloat64()).isWithin(0.0001).of(1.23);
assertThat(v.toString()).isEqualTo("1.23");
+ assertEquals("1.23", v.getAsString());
}
@Test
@@ -186,6 +200,7 @@ public void float64Wrapper() {
assertThat(v.isNull()).isFalse();
assertThat(v.getFloat64()).isWithin(0.0001).of(1.23);
assertThat(v.toString()).isEqualTo("1.23");
+ assertEquals("1.23", v.getAsString());
}
@Test
@@ -196,6 +211,7 @@ public void float64WrapperNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat64);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -205,6 +221,7 @@ public void numeric() {
assertThat(v.isNull()).isFalse();
assertThat(v.getNumeric()).isEqualTo(BigDecimal.valueOf(123, 2));
assertThat(v.toString()).isEqualTo("1.23");
+ assertEquals("1.23", v.getAsString());
}
@Test
@@ -216,6 +233,7 @@ public void pgNumeric() {
assertEquals(BigDecimal.valueOf(12345678, 4), value.getNumeric());
assertEquals(1234.5678D, value.getFloat64(), 0.00001);
assertEquals("1234.5678", value.toString());
+ assertEquals("1234.5678", value.getAsString());
}
@Test
@@ -227,6 +245,7 @@ public void pgNumericNaN() {
assertThrows(NumberFormatException.class, value::getNumeric);
assertEquals(Double.NaN, value.getFloat64(), 0.00001);
assertEquals("NaN", value.toString());
+ assertEquals("NaN", value.getAsString());
}
@Test
@@ -384,6 +403,7 @@ public void numericNull() {
IllegalStateException e = assertThrows(IllegalStateException.class, v::getNumeric);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -399,6 +419,7 @@ public void pgNumericNull() {
assertTrue("exception should mention value is null", e2.getMessage().contains("null value"));
final IllegalStateException e3 = assertThrows(IllegalStateException.class, value::getFloat64);
assertTrue("exception should mention value is null", e3.getMessage().contains("null value"));
+ assertEquals("NULL", value.getAsString());
}
@Test
@@ -411,6 +432,7 @@ public void pgNumericInvalid() {
assertEquals("INVALID", value.getString());
assertThrows(NumberFormatException.class, value::getNumeric);
assertThrows(NumberFormatException.class, value::getFloat64);
+ assertEquals("INVALID", value.getAsString());
}
@Test
@@ -419,6 +441,7 @@ public void string() {
assertThat(v.getType()).isEqualTo(Type.string());
assertThat(v.isNull()).isFalse();
assertThat(v.getString()).isEqualTo("abc");
+ assertEquals("abc", v.getAsString());
}
@Test
@@ -429,6 +452,7 @@ public void stringNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getString);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -439,6 +463,7 @@ public void stringLong() {
assertThat(v.toString()).hasLength(36);
assertThat(v.toString()).startsWith(str.substring(0, 36 - 3));
assertThat(v.toString()).endsWith("...");
+ assertEquals(str, v.getAsString());
}
@Test
@@ -449,6 +474,7 @@ public void json() {
assertFalse(v.isNull());
assertEquals(json, v.getJson());
assertEquals(json, v.getString());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -459,6 +485,7 @@ public void jsonNull() {
assertEquals(NULL_STRING, v.toString());
assertThrowsWithMessage(v::getJson, "null value");
assertThrowsWithMessage(v::getString, "null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -466,6 +493,7 @@ public void jsonEmpty() {
String json = "{}";
Value v = Value.json(json);
assertEquals(json, v.getJson());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -473,6 +501,7 @@ public void jsonWithEmptyArray() {
String json = "[]";
Value v = Value.json(json);
assertEquals(json, v.getJson());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -481,6 +510,7 @@ public void jsonWithArray() {
"[{\"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\"}]";
Value v = Value.json(json);
assertEquals(json, v.getJson());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -489,6 +519,7 @@ public void jsonNested() {
"[{\"id\":\"0001\",\"type\":\"donut\",\"name\":\"Cake\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"},{\"id\":\"1002\",\"type\":\"Chocolate\"},{\"id\":\"1003\",\"type\":\"Blueberry\"},{\"id\":\"1004\",\"type\":\"Devil's Food\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5005\",\"type\":\"Sugar\"},{\"id\":\"5007\",\"type\":\"Powdered Sugar\"},{\"id\":\"5006\",\"type\":\"Chocolate with Sprinkles\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]},{\"id\":\"0002\",\"type\":\"donut\",\"name\":\"Raised\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5005\",\"type\":\"Sugar\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]},{\"id\":\"0003\",\"type\":\"donut\",\"name\":\"Old Fashioned\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"},{\"id\":\"1002\",\"type\":\"Chocolate\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]}]";
Value v = Value.json(json);
assertEquals(json, v.getJson());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -499,6 +530,7 @@ public void testPgJsonb() {
assertFalse(v.isNull());
assertEquals(json, v.getPgJsonb());
assertEquals(json, v.getString());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -509,6 +541,7 @@ public void testPgJsonbNull() {
assertEquals(NULL_STRING, v.toString());
assertThrowsWithMessage(v::getPgJsonb, "null value");
assertThrowsWithMessage(v::getString, "null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -516,6 +549,7 @@ public void testPgJsonbEmpty() {
String json = "{}";
Value v = Value.pgJsonb(json);
assertEquals(json, v.getPgJsonb());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -523,6 +557,7 @@ public void testPgJsonbWithEmptyArray() {
String json = "[]";
Value v = Value.pgJsonb(json);
assertEquals(json, v.getPgJsonb());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -531,6 +566,7 @@ public void testPgJsonbWithArray() {
"[{\"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\"}]";
Value v = Value.pgJsonb(json);
assertEquals(json, v.getPgJsonb());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -539,6 +575,7 @@ public void testPgJsonbNested() {
"[{\"id\":\"0001\",\"type\":\"donut\",\"name\":\"Cake\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"},{\"id\":\"1002\",\"type\":\"Chocolate\"},{\"id\":\"1003\",\"type\":\"Blueberry\"},{\"id\":\"1004\",\"type\":\"Devil's Food\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5005\",\"type\":\"Sugar\"},{\"id\":\"5007\",\"type\":\"Powdered Sugar\"},{\"id\":\"5006\",\"type\":\"Chocolate with Sprinkles\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]},{\"id\":\"0002\",\"type\":\"donut\",\"name\":\"Raised\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5005\",\"type\":\"Sugar\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]},{\"id\":\"0003\",\"type\":\"donut\",\"name\":\"Old Fashioned\",\"ppu\":0.55,\"batters\":{\"batter\":[{\"id\":\"1001\",\"type\":\"Regular\"},{\"id\":\"1002\",\"type\":\"Chocolate\"}]},\"topping\":[{\"id\":\"5001\",\"type\":\"None\"},{\"id\":\"5002\",\"type\":\"Glazed\"},{\"id\":\"5003\",\"type\":\"Chocolate\"},{\"id\":\"5004\",\"type\":\"Maple\"}]}]";
Value v = Value.pgJsonb(json);
assertEquals(json, v.getPgJsonb());
+ assertEquals(json, v.getAsString());
}
@Test
@@ -548,7 +585,8 @@ public void bytes() {
assertThat(v.getType()).isEqualTo(Type.bytes());
assertThat(v.isNull()).isFalse();
assertThat(v.getBytes()).isSameInstanceAs(bytes);
- assertThat(v.toString()).isEqualTo(bytes.toString());
+ assertThat(v.toString()).isEqualTo(bytes.toBase64());
+ assertEquals(Base64.getEncoder().encodeToString(bytes.toByteArray()), v.getAsString());
}
@Test
@@ -556,7 +594,8 @@ public void bytesUnprintable() {
ByteArray bytes = ByteArray.copyFrom(new byte[] {'a', 0, 15, -1, 'e'});
Value v = Value.bytes(bytes);
assertThat(v.getBytes()).isSameInstanceAs(bytes);
- assertThat(v.toString()).isEqualTo(bytes.toString());
+ assertThat(v.toString()).isEqualTo(bytes.toBase64());
+ assertEquals(Base64.getEncoder().encodeToString(bytes.toByteArray()), v.getAsString());
}
@Test
@@ -567,6 +606,7 @@ public void bytesNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getBytes);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -579,6 +619,7 @@ public void timestamp() {
assertThat(v.isCommitTimestamp()).isFalse();
assertThat(v.getTimestamp()).isSameInstanceAs(t);
assertThat(v.toString()).isEqualTo(timestamp);
+ assertEquals(timestamp, v.getAsString());
}
@Test
@@ -590,6 +631,7 @@ public void timestampNull() {
assertThat(v.isCommitTimestamp()).isFalse();
IllegalStateException e = assertThrows(IllegalStateException.class, v::getTimestamp);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -606,6 +648,7 @@ public void commitTimestamp() {
.build());
IllegalStateException e = assertThrows(IllegalStateException.class, v::getTimestamp);
assertThat(e.getMessage()).contains("Commit timestamp value");
+ assertEquals("spanner.commit_timestamp()", v.getAsString());
}
@Test
@@ -617,6 +660,7 @@ public void date() {
assertThat(v.isNull()).isFalse();
assertThat(v.getDate()).isSameInstanceAs(t);
assertThat(v.toString()).isEqualTo(date);
+ assertEquals(date, v.getAsString());
}
@Test
@@ -627,6 +671,7 @@ public void dateNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getDate);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -635,6 +680,7 @@ public void boolArray() {
assertThat(v.isNull()).isFalse();
assertThat(v.getBoolArray()).containsExactly(true, false).inOrder();
assertThat(v.toString()).isEqualTo("[true,false]");
+ assertEquals("[true,false]", v.getAsString());
}
@Test
@@ -643,6 +689,7 @@ public void boolArrayRange() {
assertThat(v.isNull()).isFalse();
assertThat(v.getBoolArray()).containsExactly(false, false, true).inOrder();
assertThat(v.toString()).isEqualTo("[false,false,true]");
+ assertEquals("[false,false,true]", v.getAsString());
}
@Test
@@ -652,6 +699,7 @@ public void boolArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getBoolArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -660,6 +708,7 @@ public void boolArrayFromList() {
assertThat(v.isNull()).isFalse();
assertThat(v.getBoolArray()).containsExactly(true, null, false).inOrder();
assertThat(v.toString()).isEqualTo("[true,NULL,false]");
+ assertEquals("[true,NULL,false]", v.getAsString());
}
@Test
@@ -669,6 +718,7 @@ public void boolArrayFromListNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getBoolArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -685,6 +735,11 @@ public void boolArrayFromPlainIterable() {
Value v = Value.boolArray(plainIterable(data));
assertWithMessage(name).that(v.isNull()).isFalse();
assertWithMessage(name).that(v.getBoolArray()).containsExactly((Object[]) data).inOrder();
+ assertEquals(
+ Arrays.stream(data)
+ .map(element -> String.valueOf(element).replace("null", "NULL"))
+ .collect(Collectors.joining(",", "[", "]")),
+ v.getAsString());
}
}
@@ -701,6 +756,7 @@ public void int64Array() {
assertThat(v.isNull()).isFalse();
assertThat(v.getInt64Array()).containsExactly(1L, 2L).inOrder();
assertThat(v.toString()).isEqualTo("[1,2]");
+ assertEquals("[1,2]", v.getAsString());
}
@Test
@@ -709,6 +765,7 @@ public void int64ArrayRange() {
assertThat(v.isNull()).isFalse();
assertThat(v.getInt64Array()).containsExactly(2L, 3L, 4L).inOrder();
assertThat(v.toString()).isEqualTo("[2,3,4]");
+ assertEquals("[2,3,4]", v.getAsString());
}
@Test
@@ -718,6 +775,7 @@ public void int64ArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64Array);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -726,6 +784,7 @@ public void int64ArrayWrapper() {
assertThat(v.isNull()).isFalse();
assertThat(v.getInt64Array()).containsExactly(1L, null, 3L).inOrder();
assertThat(v.toString()).isEqualTo("[1,NULL,3]");
+ assertEquals("[1,NULL,3]", v.getAsString());
}
@Test
@@ -735,6 +794,7 @@ public void int64ArrayWrapperNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64Array);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -757,6 +817,7 @@ public void float64Array() {
assertThat(v.isNull()).isFalse();
assertThat(v.getFloat64Array()).containsExactly(.1d, .2d).inOrder();
assertThat(v.toString()).isEqualTo("[0.1,0.2]");
+ assertEquals("[0.1,0.2]", v.getAsString());
}
@Test
@@ -765,6 +826,7 @@ public void float64ArrayRange() {
assertThat(v.isNull()).isFalse();
assertThat(v.getFloat64Array()).containsExactly(.2d, .3d, .4d).inOrder();
assertThat(v.toString()).isEqualTo("[0.2,0.3,0.4]");
+ assertEquals("[0.2,0.3,0.4]", v.getAsString());
}
@Test
@@ -774,6 +836,7 @@ public void float64ArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat64Array);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -782,6 +845,7 @@ public void float64ArrayWrapper() {
assertThat(v.isNull()).isFalse();
assertThat(v.getFloat64Array()).containsExactly(.1d, null, .3d).inOrder();
assertThat(v.toString()).isEqualTo("[0.1,NULL,0.3]");
+ assertEquals("[0.1,NULL,0.3]", v.getAsString());
}
@Test
@@ -791,6 +855,7 @@ public void float64ArrayWrapperNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getFloat64Array);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -809,6 +874,7 @@ public void numericArray() {
.containsExactly(new BigDecimal("0.1"), null, new BigDecimal("0.3"))
.inOrder();
assertThat(v.toString()).isEqualTo("[0.1,NULL,0.3]");
+ assertEquals("[0.1,NULL,0.3]", v.getAsString());
}
@Test
@@ -823,6 +889,7 @@ public void pgNumericArray() {
assertEquals(1.23D, float64Array.get(0), 0.001);
assertNull(float64Array.get(1));
assertEquals(1.24D, float64Array.get(2), 0.001);
+ assertEquals("[1.23,NULL,1.24]", value.getAsString());
}
@Test
@@ -835,6 +902,7 @@ public void pgNumericArrayWithNaNs() {
assertEquals(1.23D, float64Array.get(0), 0.001);
assertNull(float64Array.get(1));
assertEquals(Double.NaN, float64Array.get(2), 0.001);
+ assertEquals("[1.23,NULL,NaN]", value.getAsString());
}
@Test
@@ -845,6 +913,7 @@ public void numericArrayNull() {
IllegalStateException e = assertThrows(IllegalStateException.class, v::getNumericArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -862,6 +931,7 @@ public void pgNumericArrayNull() {
final IllegalStateException e3 =
assertThrows(IllegalStateException.class, value::getFloat64Array);
assertTrue("exception should mention value is null", e3.getMessage().contains("null value"));
+ assertEquals("NULL", value.getAsString());
}
@Test
@@ -888,6 +958,7 @@ public void stringArray() {
assertThat(v.isNull()).isFalse();
assertThat(v.getStringArray()).containsExactly("a", null, "c").inOrder();
assertThat(v.toString()).isEqualTo("[a,NULL,c]");
+ assertEquals("[a,NULL,c]", v.getAsString());
}
@Test
@@ -897,6 +968,7 @@ public void stringArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getStringArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -916,6 +988,7 @@ public void jsonArray() {
assertArrayEquals(new String[] {one, two, three}, v.getJsonArray().toArray());
assertEquals("[{},NULL,{\"color\":\"red\",\"value\":\"#f00\"}]", v.toString());
assertArrayEquals(new String[] {one, two, three}, v.getStringArray().toArray());
+ assertEquals("[{},NULL,{\"color\":\"red\",\"value\":\"#f00\"}]", v.getAsString());
}
@Test
@@ -925,17 +998,13 @@ public void jsonArrayNull() {
assertEquals(NULL_STRING, v.toString());
assertThrowsWithMessage(v::getJsonArray, "null value");
assertThrowsWithMessage(v::getStringArray, "null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
public void jsonArrayTryGetBytesArray() {
- Value value = Value.jsonArray(Arrays.asList("{}"));
- try {
- value.getBytesArray();
- fail("Expected exception");
- } catch (IllegalStateException e) {
- assertThat(e.getMessage().contains("Expected: ARRAY actual: ARRAY"));
- }
+ Value value = Value.jsonArray(Collections.singletonList("{}"));
+ assertThrowsWithMessage(value::getBytesArray, "Expected: ARRAY actual: ARRAY");
}
@Test
@@ -954,6 +1023,7 @@ public void testPgJsonbArray() {
assertArrayEquals(new String[] {one, two, three}, v.getPgJsonbArray().toArray());
assertEquals("[{},NULL,{\"color\":\"red\",\"value\":\"#f00\"}]", v.toString());
assertArrayEquals(new String[] {one, two, three}, v.getStringArray().toArray());
+ assertEquals("[{},NULL,{\"color\":\"red\",\"value\":\"#f00\"}]", v.getAsString());
}
@Test
@@ -963,6 +1033,7 @@ public void testPgJsonbArrayNull() {
assertEquals(NULL_STRING, v.toString());
assertThrowsWithMessage(v::getPgJsonbArray, "null value");
assertThrowsWithMessage(v::getStringArray, "null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -986,7 +1057,18 @@ public void bytesArray() {
Value v = Value.bytesArray(Arrays.asList(a, null, c));
assertThat(v.isNull()).isFalse();
assertThat(v.getBytesArray()).containsExactly(a, null, c).inOrder();
- assertThat(v.toString()).isEqualTo(String.format("[%s,NULL,%s]", a, c));
+ assertThat(v.toString())
+ .isEqualTo(
+ String.format(
+ "[%s,NULL,%s]",
+ Base64.getEncoder().encodeToString("a".getBytes(StandardCharsets.UTF_8)),
+ Base64.getEncoder().encodeToString("c".getBytes(StandardCharsets.UTF_8))));
+ assertEquals(
+ String.format(
+ "[%s,NULL,%s]",
+ Base64.getEncoder().encodeToString("a".getBytes(StandardCharsets.UTF_8)),
+ Base64.getEncoder().encodeToString("c".getBytes(StandardCharsets.UTF_8))),
+ v.getAsString());
}
@Test
@@ -996,6 +1078,7 @@ public void bytesArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getBytesArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1017,6 +1100,7 @@ public void timestampArray() {
.containsExactly(Timestamp.parseTimestamp(t1), null, Timestamp.parseTimestamp(t2))
.inOrder();
assertThat(v.toString()).isEqualTo("[" + t1 + ",NULL," + t2 + "]");
+ assertEquals(String.format("[%s,NULL,%s]", t1, t2), v.getAsString());
}
@Test
@@ -1026,6 +1110,7 @@ public void timestampArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getTimestampArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1039,6 +1124,7 @@ public void dateArray() {
.containsExactly(Date.parseDate(d1), null, Date.parseDate(d2))
.inOrder();
assertThat(v.toString()).isEqualTo("[" + d1 + ",NULL," + d2 + "]");
+ assertEquals(String.format("[%s,NULL,%s]", d1, d2), v.getAsString());
}
@Test
@@ -1048,6 +1134,7 @@ public void dateArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getDateArray);
assertThat(e.getMessage()).contains("null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1058,6 +1145,7 @@ public void struct() {
assertThat(v1.isNull()).isFalse();
assertThat(v1.getStruct()).isEqualTo(struct);
assertThat(v1.toString()).isEqualTo("[v1, 30]");
+ assertEquals("[v1, 30]", v1.getAsString());
Value v2 = Value.struct(struct.getType(), struct);
assertThat(v2).isEqualTo(v1);
@@ -1083,6 +1171,7 @@ public void nullStruct() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
NullPointerException e = assertThrows(NullPointerException.class, () -> Value.struct(null));
assertThat(e.getMessage()).contains("Illegal call to create a NULL struct value.");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1095,6 +1184,7 @@ public void nullStructGetter() {
assertThat(v.isNull()).isTrue();
IllegalStateException e = assertThrows(IllegalStateException.class, v::getStruct);
assertThat(e.getMessage()).contains("Illegal call to getter of null value.");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1163,6 +1253,7 @@ public void structArray() {
assertThat(v.getType().getArrayElementType()).isEqualTo(elementType);
assertThat(v.getStructArray()).isEqualTo(arrayElements);
assertThat(v.toString()).isEqualTo("[[v1, 1],NULL,NULL,[v3, 3]]");
+ assertEquals("[[v1, 1],NULL,NULL,[v3, 3]]", v.getAsString());
}
@Test
@@ -1178,6 +1269,7 @@ public void structArrayNull() {
assertThat(v.toString()).isEqualTo(NULL_STRING);
IllegalStateException e = assertThrows(IllegalStateException.class, v::getStructArray);
assertThat(e.getMessage()).contains("Illegal call to getter of null value");
+ assertEquals("NULL", v.getAsString());
}
@Test
@@ -1792,6 +1884,48 @@ public void testEqualsHashCode() {
tester.testEquals();
}
+ @Test
+ public void testGetAsString() {
+ assertEquals("true", Value.bool(true).getAsString());
+ assertEquals("false", Value.bool(false).getAsString());
+
+ assertEquals("1", Value.int64(1L).getAsString());
+ assertEquals(String.valueOf(Long.MAX_VALUE), Value.int64(Long.MAX_VALUE).getAsString());
+ assertEquals(String.valueOf(Long.MIN_VALUE), Value.int64(Long.MIN_VALUE).getAsString());
+
+ assertEquals("3.14", Value.float64(3.14d).getAsString());
+ assertEquals("NaN", Value.float64(Double.NaN).getAsString());
+ assertEquals(String.valueOf(Double.MIN_VALUE), Value.float64(Double.MIN_VALUE).getAsString());
+ assertEquals(String.valueOf(Double.MAX_VALUE), Value.float64(Double.MAX_VALUE).getAsString());
+
+ assertEquals("3.14", Value.numeric(new BigDecimal("3.14")).getAsString());
+ assertEquals(
+ "123456789.123456789", Value.numeric(new BigDecimal("123456789.123456789")).getAsString());
+
+ assertEquals("3.14", Value.pgNumeric("3.14").getAsString());
+ assertEquals("123456789.123456789", Value.pgNumeric("123456789.123456789").getAsString());
+ assertEquals("NaN", Value.pgNumeric("NaN").getAsString());
+
+ assertEquals(Strings.repeat("foo", 36), Value.string(Strings.repeat("foo", 36)).getAsString());
+ assertEquals(Strings.repeat("foo", 36), Value.json(Strings.repeat("foo", 36)).getAsString());
+ assertEquals(Strings.repeat("foo", 36), Value.pgJsonb(Strings.repeat("foo", 36)).getAsString());
+
+ assertEquals(
+ "2023-01-10T18:59:00Z",
+ Value.timestamp(Timestamp.parseTimestamp("2023-01-10T18:59:00Z")).getAsString());
+ assertEquals("2023-01-10", Value.date(Date.parseDate("2023-01-10")).getAsString());
+
+ Random random = new Random();
+ byte[] bytes = new byte[random.nextInt(256)];
+ assertEquals(
+ Base64.getEncoder().encodeToString(bytes),
+ Value.bytes(ByteArray.copyFrom(bytes)).getAsString());
+ assertEquals(
+ Base64.getEncoder().encodeToString(bytes),
+ Value.internalBytes(new LazyByteArray(Base64.getEncoder().encodeToString(bytes)))
+ .getAsString());
+ }
+
@Test
public void serialization() {
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..f8415fb00eb 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
@@ -39,7 +39,7 @@
* of Cloud Spanner filled with random data.
*/
public class RandomResultSetGenerator {
- private static Type[] generateTypes(Dialect dialect) {
+ public static Type[] generateAllTypes(Dialect dialect) {
return new Type[] {
Type.newBuilder().setCode(TypeCode.BOOL).build(),
Type.newBuilder().setCode(TypeCode.INT64).build(),
@@ -109,7 +109,7 @@ private static Type[] generateTypes(Dialect dialect) {
};
}
- private static ResultSetMetadata generateMetadata(Type[] types) {
+ public static ResultSetMetadata generateAllTypesMetadata(Type[] types) {
StructType.Builder rowTypeBuilder = StructType.newBuilder();
for (int col = 0; col < types.length; col++) {
rowTypeBuilder.addFields(Field.newBuilder().setName("COL" + col).setType(types[col])).build();
@@ -132,8 +132,8 @@ public RandomResultSetGenerator(int rowCount) {
public RandomResultSetGenerator(int rowCount, Dialect dialect) {
this.rowCount = rowCount;
this.dialect = dialect;
- this.types = generateTypes(dialect);
- this.metadata = generateMetadata(types);
+ this.types = generateAllTypes(dialect);
+ this.metadata = generateAllTypesMetadata(types);
}
public ResultSet generate() {
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java
index 2428e75c994..3179093139e 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java
@@ -21,7 +21,9 @@
import static com.google.cloud.spanner.Type.json;
import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -42,6 +44,7 @@
import com.google.cloud.spanner.ParallelIntegrationTest;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.Value;
@@ -52,6 +55,7 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -431,6 +435,55 @@ public void writeBytes() {
assertThat(row.getBytes(0)).isEqualTo(data);
}
+ @Test
+ public void writeBytesAsString() {
+ Random random = new Random();
+ byte[] data = new byte[256];
+ random.nextBytes(data);
+ String base64 = Base64.getEncoder().encodeToString(data);
+ write(baseInsert().set("BytesValue").to(base64).build());
+ Struct row = readLastRow("BytesValue");
+ assertFalse(row.isNull(0));
+ assertArrayEquals(data, row.getBytes(0).toByteArray());
+ assertEquals(base64, row.getValue(0).getAsString());
+ }
+
+ @Test
+ public void writeBytesAsStringUsingDml() {
+ Random random = new Random();
+ byte[] data = new byte[256];
+ random.nextBytes(data);
+ String base64 = Base64.getEncoder().encodeToString(data);
+ Long updateCount =
+ client
+ .readWriteTransaction()
+ .run(
+ transaction ->
+ transaction.executeUpdate(
+ Statement.newBuilder(
+ "insert into T (BytesValue, K) values ("
+ + queryParamString(1)
+ + ", "
+ + queryParamString(2)
+ + ")")
+ .bind("p1")
+ .to(Value.bytesFromBase64(base64))
+ .bind("p2")
+ .to(lastKey = uniqueString())
+ .build()));
+ assertNotNull(updateCount);
+ assertEquals(1L, updateCount.longValue());
+
+ Struct row = readLastRow("BytesValue");
+ assertFalse(row.isNull(0));
+ assertArrayEquals(data, row.getBytes(0).toByteArray());
+ assertEquals(base64, row.getValue(0).getAsString());
+ }
+
+ String queryParamString(int index) {
+ return dialect.dialect == Dialect.GOOGLE_STANDARD_SQL ? "@p" + index : "$" + index;
+ }
+
@Test
public void writeBytesRandom() {
// Pseudo-random test for byte encoding. We explicitly set a random seed so that multiple
From b5fdbc0accdaaf1f63c62c1837d72bb378dc8f43 Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Mon, 6 Feb 2023 05:12:18 +0000
Subject: [PATCH 03/10] deps: update dependency
com.google.cloud:google-cloud-shared-dependencies to v3.2.0 (#2264)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* deps: update dependency com.google.cloud:google-cloud-shared-dependencies to v3.2.0
* 🦉 Updates from OwlBot post-processor
See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md
---------
Co-authored-by: Owl Bot
---
README.md | 2 +-
pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 93619201701..d5db9eb8177 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ If you are using Maven without BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:
```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.5.0')
+implementation platform('com.google.cloud:libraries-bom:26.6.0')
implementation 'com.google.cloud:google-cloud-spanner'
```
diff --git a/pom.xml b/pom.xml
index b56c36dd17e..db45c17d5fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
UTF-8
github
google-cloud-spanner-parent
- 3.1.2
+ 3.2.0