func) {
+ ListValue.Builder listValueBuilder = ListValue.newBuilder();
+ SpannerTypeConverter.processIterable(
+ value,
+ iterator,
+ (val) -> com.google.protobuf.Value.newBuilder().setStringValue(func.apply(val)).build(),
+ listValueBuilder::addValues);
+ return Value.untyped(
+ com.google.protobuf.Value.newBuilder().setListValue(listValueBuilder.build()).build());
+ }
+
+ static ZonedDateTime atUTC(LocalDateTime localDateTime) {
+ return atUTC(localDateTime.atZone(ZoneId.systemDefault()));
+ }
+
+ static ZonedDateTime atUTC(OffsetDateTime localDateTime) {
+ return localDateTime.atZoneSameInstant(UTC_ZONE);
+ }
+
+ static ZonedDateTime atUTC(ZonedDateTime localDateTime) {
+ return localDateTime.withZoneSameInstant(UTC_ZONE);
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java
index a9cf3e7dec4..a454fbb689b 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java
@@ -20,6 +20,8 @@
import static com.google.common.base.Preconditions.checkState;
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
+import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
@@ -246,4 +248,103 @@ StringBuilder toString(StringBuilder b) {
}
return b;
}
+
+ /**
+ * Factory for creating {@link Statement}s with unnamed parameters.
+ *
+ * This class is primarily intended for framework developers who want to integrate the Spanner
+ * client with a framework that uses unnamed parameters. Developers who want to use the Spanner
+ * client in their application, should use named parameters.
+ *
+ *
+ *
+ *
Usage Example
+ *
+ * Simple SQL query
+ *
+ * {@code
+ * Statement statement = databaseClient.getStatementFactory()
+ * .withUnnamedParameters("SELECT * FROM TABLE WHERE ID = ?", 10L)
+ * }
+ *
+ * SQL query with multiple parameters
+ *
+ * {@code
+ * long id = 10L;
+ * String name = "google";
+ * List phoneNumbers = Arrays.asList("1234567890", "0987654321");
+ * Statement statement = databaseClient.getStatementFactory()
+ * .withUnnamedParameters("INSERT INTO TABLE (ID, name, phonenumbers) VALUES(?, ?, ?)", id, name, phoneNumbers)
+ * }
+ *
+ * How to use arrays with the IN operator
+ *
+ * {@code
+ * long[] ids = {10L, 12L, 1483L};
+ * Statement statement = databaseClient.getStatementFactory()
+ * .withUnnamedParameters("SELECT * FROM TABLE WHERE ID = UNNEST(?)", ids)
+ * }
+ *
+ * @see DatabaseClient#getStatementFactory()
+ * @see StatementFactory#withUnnamedParameters(String, Object...)
+ */
+ public static final class StatementFactory {
+ private final Dialect dialect;
+
+ StatementFactory(Dialect dialect) {
+ this.dialect = dialect;
+ }
+
+ public Statement of(String sql) {
+ return Statement.of(sql);
+ }
+
+ /**
+ * This function accepts a SQL statement with unnamed parameters (?) and accepts a list of
+ * objects that should be used as the values for those parameters. Primitive types are
+ * supported.
+ *
+ * For parameters of type DATE, the following types are supported
+ *
+ *
+ * - {@link java.time.LocalDate}
+ *
- {@link com.google.cloud.Date}
+ *
+ *
+ * For parameters of type TIMESTAMP, the following types are supported. Note that Spanner
+ * stores all timestamps in UTC. Instances of ZonedDateTime and OffsetDateTime that use other
+ * timezones than UTC, will be converted to the corresponding UTC values before being sent to
+ * Spanner. Instances of LocalDateTime will be converted to a ZonedDateTime using the system
+ * default timezone, and then converted to UTC before being sent to Spanner.
+ *
+ *
+ * - {@link java.time.LocalDateTime}
+ *
- {@link java.time.OffsetDateTime}
+ *
- {@link java.time.ZonedDateTime}
+ *
+ *
+ *
+ *
+ * @param sql SQL statement with unnamed parameters denoted as ?
+ * @param values positional list of values for the unnamed parameters in the SQL string
+ * @return Statement a statement that can be executed on Spanner
+ * @see DatabaseClient#getStatementFactory
+ */
+ public Statement withUnnamedParameters(String sql, Object... values) {
+ Map parameters = getUnnamedParametersMap(values);
+ AbstractStatementParser statementParser = AbstractStatementParser.getInstance(this.dialect);
+ ParametersInfo parametersInfo =
+ statementParser.convertPositionalParametersToNamedParameters('?', sql);
+ return new Statement(parametersInfo.sqlWithNamedParameters, parameters, null);
+ }
+
+ private Map getUnnamedParametersMap(Object[] values) {
+ Map parameters = new HashMap<>();
+ int index = 1;
+ for (Object value : values) {
+ parameters.put("p" + (index++), Value.toValue(value));
+ }
+ return parameters;
+ }
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
index 6cb68eeee4a..5befba04e57 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
@@ -16,6 +16,14 @@
package com.google.cloud.spanner;
+import static com.google.cloud.spanner.SpannerTypeConverter.atUTC;
+import static com.google.cloud.spanner.SpannerTypeConverter.convertLocalDateToSpannerDate;
+import static com.google.cloud.spanner.SpannerTypeConverter.convertToISO8601;
+import static com.google.cloud.spanner.SpannerTypeConverter.convertToTypedIterable;
+import static com.google.cloud.spanner.SpannerTypeConverter.createUntypedArrayValue;
+import static com.google.cloud.spanner.SpannerTypeConverter.createUntypedIterableValue;
+import static com.google.cloud.spanner.SpannerTypeConverter.createUntypedStringValue;
+
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
@@ -39,16 +47,22 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -824,6 +838,159 @@ public static Value structArray(Type elementType, @Nullable Iterable v)
private Value() {}
+ static Value toValue(Object value) {
+ if (value == null) {
+ return Value.untyped(NULL_PROTO);
+ }
+ if (value instanceof Value) {
+ return (Value) value;
+ }
+ if (value instanceof Boolean) {
+ return Value.bool((Boolean) value);
+ }
+ if (value instanceof Long || value instanceof Integer) {
+ return createUntypedStringValue(String.valueOf(value));
+ }
+ if (value instanceof Float) {
+ return Value.float32((Float) value);
+ }
+ if (value instanceof Double) {
+ return Value.float64((Double) value);
+ }
+ if (value instanceof BigDecimal) {
+ return Value.numeric((BigDecimal) value);
+ }
+ if (value instanceof ByteArray) {
+ return Value.bytes((ByteArray) value);
+ }
+ if (value instanceof byte[]) {
+ return Value.bytes(ByteArray.copyFrom((byte[]) value));
+ }
+ if (value instanceof Date) {
+ return Value.date((Date) value);
+ }
+ if (value instanceof LocalDate) {
+ return Value.date(convertLocalDateToSpannerDate((LocalDate) value));
+ }
+ if (value instanceof LocalDateTime) {
+ return createUntypedStringValue(convertToISO8601(atUTC((LocalDateTime) value)));
+ }
+ if (value instanceof OffsetDateTime) {
+ return createUntypedStringValue(convertToISO8601(atUTC((OffsetDateTime) value)));
+ }
+ if (value instanceof ZonedDateTime) {
+ return createUntypedStringValue(convertToISO8601(atUTC((ZonedDateTime) value)));
+ }
+ if (value instanceof ProtocolMessageEnum) {
+ return Value.protoEnum((ProtocolMessageEnum) value);
+ }
+ if (value instanceof AbstractMessage) {
+ return Value.protoMessage((AbstractMessage) value);
+ }
+ if (value instanceof Interval) {
+ return Value.interval((Interval) value);
+ }
+ if (value instanceof Struct) {
+ return Value.struct((Struct) value);
+ }
+ if (value instanceof Timestamp) {
+ return Value.timestamp((Timestamp) value);
+ }
+ if (value instanceof Iterable>) {
+ Iterator> iterator = ((Iterable>) value).iterator();
+ if (!iterator.hasNext()) {
+ return createUntypedArrayValue(Stream.empty());
+ }
+ Object object = iterator.next();
+ if (object instanceof Boolean) {
+ return Value.boolArray(convertToTypedIterable((Boolean) object, iterator));
+ }
+ if (object instanceof Integer) {
+ return createUntypedIterableValue((Integer) object, iterator, String::valueOf);
+ }
+ if (object instanceof Long) {
+ return createUntypedIterableValue((Long) object, iterator, String::valueOf);
+ }
+ if (object instanceof Float) {
+ return Value.float32Array(convertToTypedIterable((Float) object, iterator));
+ }
+ if (object instanceof Double) {
+ return Value.float64Array(convertToTypedIterable((Double) object, iterator));
+ }
+ if (object instanceof BigDecimal) {
+ return Value.numericArray(convertToTypedIterable((BigDecimal) object, iterator));
+ }
+ if (object instanceof ByteArray) {
+ return Value.bytesArray(convertToTypedIterable((ByteArray) object, iterator));
+ }
+ if (object instanceof byte[]) {
+ return Value.bytesArray(
+ SpannerTypeConverter.convertToTypedIterable(
+ ByteArray::copyFrom, (byte[]) object, iterator));
+ }
+ if (object instanceof Interval) {
+ return Value.intervalArray(convertToTypedIterable((Interval) object, iterator));
+ }
+ if (object instanceof Timestamp) {
+ return Value.timestampArray(convertToTypedIterable((Timestamp) object, iterator));
+ }
+ if (object instanceof Date) {
+ return Value.dateArray(convertToTypedIterable((Date) object, iterator));
+ }
+ if (object instanceof LocalDate) {
+ return Value.dateArray(
+ SpannerTypeConverter.convertToTypedIterable(
+ SpannerTypeConverter::convertLocalDateToSpannerDate, (LocalDate) object, iterator));
+ }
+ if (object instanceof LocalDateTime) {
+ return createUntypedIterableValue(
+ (LocalDateTime) object, iterator, val -> convertToISO8601(atUTC(val)));
+ }
+ if (object instanceof OffsetDateTime) {
+ return createUntypedIterableValue(
+ (OffsetDateTime) object, iterator, val -> convertToISO8601(atUTC(val)));
+ }
+ if (object instanceof ZonedDateTime) {
+ return createUntypedIterableValue(
+ (ZonedDateTime) object, iterator, val -> convertToISO8601(atUTC(val)));
+ }
+ }
+
+ // array and primitive array
+ if (value instanceof Boolean[]) {
+ return Value.boolArray(Arrays.asList((Boolean[]) value));
+ }
+ if (value instanceof boolean[]) {
+ return Value.boolArray((boolean[]) value);
+ }
+ if (value instanceof Float[]) {
+ return Value.float32Array(Arrays.asList((Float[]) value));
+ }
+ if (value instanceof float[]) {
+ return Value.float32Array((float[]) value);
+ }
+ if (value instanceof Double[]) {
+ return Value.float64Array(Arrays.asList((Double[]) value));
+ }
+ if (value instanceof double[]) {
+ return Value.float64Array((double[]) value);
+ }
+ if (value instanceof Long[]) {
+ return createUntypedArrayValue(Arrays.stream((Long[]) value));
+ }
+ if (value instanceof long[]) {
+ return createUntypedArrayValue(Arrays.stream((long[]) value).boxed());
+ }
+ if (value instanceof Integer[]) {
+ return createUntypedArrayValue(Arrays.stream((Integer[]) value));
+ }
+ if (value instanceof int[]) {
+ return createUntypedArrayValue(Arrays.stream((int[]) value).boxed());
+ }
+
+ return createUntypedStringValue(value);
+ }
+
/** Returns the type of this value. This will return a type even if {@code isNull()} is true. */
public abstract Type getType();
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 0fb4af2e8c7..70209917f0b 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
@@ -4896,6 +4896,144 @@ public void testMetadataUnknownTypes() {
}
}
+ @Test
+ public void testStatementWithUnnamedParameters() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client.getStatementFactory().withUnnamedParameters("select id from test where b=?", true);
+ Statement generatedStatement =
+ Statement.newBuilder("select id from test where b=@p1").bind("p1").to(true).build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testStatementWithUnnamedParametersAndSingleLineComment() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client
+ .getStatementFactory()
+ .withUnnamedParameters(
+ "-- comment about ? in the statement\nselect id from test where b=?", true);
+ Statement generatedStatement =
+ Statement.newBuilder("-- comment about ? in the statement\nselect id from test where b=@p1")
+ .bind("p1")
+ .to(true)
+ .build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testStatementWithUnnamedParametersAndSingleLineCommentWithHash() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client
+ .getStatementFactory()
+ .withUnnamedParameters(
+ "# comment about ? in the statement\nselect id from test where b=?", true);
+ Statement generatedStatement =
+ Statement.newBuilder("# comment about ? in the statement\nselect id from test where b=@p1")
+ .bind("p1")
+ .to(true)
+ .build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testStatementWithUnnamedParametersAndMultiLineComment() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client
+ .getStatementFactory()
+ .withUnnamedParameters(
+ "# comment about ? in the statement\nselect id from test\n /* This is a ? comment \n about ? */ \n where b=? # this is a inline command about ?",
+ true);
+ Statement generatedStatement =
+ Statement.newBuilder(
+ "# comment about ? in the statement\nselect id from test\n /* This is a ? comment \n about ? */ \n where b=@p1 # this is a inline command about ?")
+ .bind("p1")
+ .to(true)
+ .build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testStatementWithUnnamedParametersAndStringLiteralWithQuestionMark() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client
+ .getStatementFactory()
+ .withUnnamedParameters("select id from test where name = \"abc?\" AND b=?", true);
+ Statement generatedStatement =
+ Statement.newBuilder("select id from test where name = \"abc?\" AND b=@p1")
+ .bind("p1")
+ .to(true)
+ .build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ @Test
+ public void testStatementWithUnnamedParametersAndHint() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+
+ Statement statement =
+ client
+ .getStatementFactory()
+ .withUnnamedParameters("@{FORCE_INDEX=ABCDEF} select id from test where b=?", true);
+ Statement generatedStatement =
+ Statement.newBuilder("@{FORCE_INDEX=ABCDEF} select id from test where b=@p1")
+ .bind("p1")
+ .to(true)
+ .build();
+ mockSpanner.putStatementResult(StatementResult.query(generatedStatement, SELECT1_RESULTSET));
+
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(0));
+ assertFalse(resultSet.next());
+ }
+ }
+
@Test
public void testStatementWithBytesArrayParameter() {
Statement statement =
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 3c96a482340..f88d7683967 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
@@ -42,16 +42,28 @@
import com.google.common.testing.EqualsTester;
import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue;
+import com.google.protobuf.ProtocolMessageEnum;
+import com.google.spanner.v1.PartialResultSet;
+import com.google.spanner.v1.TransactionOptions.IsolationLevel;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Random;
+import java.util.Set;
+import java.util.TimeZone;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -2496,6 +2508,306 @@ public void verifyBrokenSerialization() {
reserializeAndAssert(BrokenSerializationList.of(1, 2, 3));
}
+ @Test
+ public void testToValue() {
+ Value value = Value.toValue(null);
+ assertNull(value.getType());
+ assertEquals("NULL", value.getAsString());
+
+ int i = 10;
+ value = Value.toValue(i);
+ assertNull(value.getType());
+ assertEquals("10", value.getAsString());
+
+ Integer j = 10;
+ value = Value.toValue(j);
+ assertNull(value.getType());
+ assertEquals("10", value.getAsString());
+
+ long k = 10L;
+ value = Value.toValue(k);
+ assertNull(value.getType());
+ assertEquals("10", value.getAsString());
+
+ Long l = 10L;
+ value = Value.toValue(i);
+ assertNull(value.getType());
+ assertEquals("10", value.getAsString());
+
+ boolean m = true;
+ value = Value.toValue(m);
+ assertEquals(Type.bool(), value.getType());
+ assertTrue(value.getBool());
+
+ Boolean n = true;
+ value = Value.toValue(n);
+ assertEquals(Type.bool(), value.getType());
+ assertTrue(value.getBool());
+
+ Float o = 0.3f;
+ value = Value.toValue(o);
+ assertEquals(Type.float32(), value.getType());
+ assertEquals(0.3f, value.getFloat32(), 0);
+
+ float p = 0.3f;
+ value = Value.toValue(p);
+ assertEquals(Type.float32(), value.getType());
+ assertEquals(0.3f, value.getFloat32(), 0);
+
+ Double q = 0.4d;
+ value = Value.toValue(q);
+ assertEquals(Type.float64(), value.getType());
+ assertEquals(0.4d, value.getFloat64(), 0);
+
+ double s = 0.5d;
+ value = Value.toValue(s);
+ assertEquals(Type.float64(), value.getType());
+ assertEquals(0.5d, value.getFloat64(), 0);
+
+ BigDecimal t = BigDecimal.valueOf(0.6d);
+ value = Value.toValue(t);
+ assertEquals(Type.numeric(), value.getType());
+ assertEquals(t, value.getNumeric());
+
+ ByteArray bytes = ByteArray.copyFrom("hello");
+ value = Value.toValue(bytes);
+ assertEquals(Type.bytes(), value.getType());
+ assertEquals(bytes, value.getBytes());
+
+ byte[] byteArray = "hello".getBytes();
+ value = Value.toValue(byteArray);
+ assertEquals(Type.bytes(), value.getType());
+ assertEquals(bytes, value.getBytes());
+
+ Date date = Date.fromYearMonthDay(2018, 2, 26);
+ value = Value.toValue(date);
+ assertEquals(Type.date(), value.getType());
+ assertEquals(date, value.getDate());
+
+ LocalDate localDate = LocalDate.of(2018, 2, 26);
+ value = Value.toValue(localDate);
+ assertEquals(Type.date(), value.getType());
+ assertEquals(date, value.getDate());
+
+ TimeZone defaultTimezone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
+ LocalDateTime localDateTime = LocalDateTime.of(2018, 2, 26, 11, 30, 10);
+ value = Value.toValue(localDateTime);
+ assertNull(value.getType());
+ assertEquals("2018-02-26T10:30:10.000Z", value.getAsString());
+ TimeZone.setDefault(defaultTimezone);
+
+ OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(10));
+ value = Value.toValue(offsetDateTime);
+ assertNull(value.getType());
+ assertEquals("2018-02-26T01:30:10.000Z", value.getAsString());
+
+ ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("Asia/Kolkata"));
+ value = Value.toValue(zonedDateTime);
+ assertNull(value.getType());
+ assertEquals("2018-02-26T06:00:10.000Z", value.getAsString());
+
+ ProtocolMessageEnum protocolMessageEnum = IsolationLevel.SERIALIZABLE;
+ value = Value.toValue(protocolMessageEnum);
+ assertEquals(
+ Type.protoEnum("google.spanner.v1.TransactionOptions.IsolationLevel"), value.getType());
+ assertEquals(
+ protocolMessageEnum,
+ value.getProtoEnum(
+ (val -> {
+ switch (val) {
+ case 1:
+ return IsolationLevel.SERIALIZABLE;
+ case 2:
+ return IsolationLevel.REPEATABLE_READ;
+ default:
+ return IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED;
+ }
+ })));
+
+ PartialResultSet partialResultSet =
+ PartialResultSet.newBuilder()
+ .addValues(com.google.protobuf.Value.newBuilder().setStringValue("hello").build())
+ .build();
+ value = Value.toValue(partialResultSet);
+ assertEquals(Type.proto("google.spanner.v1.PartialResultSet"), value.getType());
+ assertEquals(partialResultSet, value.getProtoMessage(PartialResultSet.getDefaultInstance()));
+
+ Interval interval = Interval.ofDays(10);
+ value = Value.toValue(interval);
+ assertEquals(Type.interval(), value.getType());
+ assertEquals(interval, value.getInterval());
+
+ Struct struct = Struct.newBuilder().set("name").to(10L).build();
+ value = Value.toValue(struct);
+ assertEquals(Type.struct(StructField.of("name", Type.int64())), value.getType());
+ assertEquals(struct, value.getStruct());
+
+ Timestamp timestamp = Timestamp.now();
+ value = Value.toValue(timestamp);
+ assertEquals(Type.timestamp(), value.getType());
+ assertEquals(timestamp, value.getTimestamp());
+
+ List expectedBoolArray = Arrays.asList(true, false);
+ boolean[] bools1 = {true, false};
+ value = Value.toValue(bools1);
+ assertEquals(Type.array(Type.bool()), value.getType());
+ assertEquals(expectedBoolArray, value.getBoolArray());
+
+ Boolean[] bools2 = {true, false};
+ value = Value.toValue(bools2);
+ assertEquals(Type.array(Type.bool()), value.getType());
+ assertEquals(expectedBoolArray, value.getBoolArray());
+
+ List expectedFloatArray = Arrays.asList(0.1f, 0.2f, 0.3f);
+ Float[] floats1 = {0.1f, 0.2f, 0.3f};
+ value = Value.toValue(floats1);
+ assertEquals(Type.array(Type.float32()), value.getType());
+ assertEquals(expectedFloatArray, value.getFloat32Array());
+
+ float[] floats2 = {0.1f, 0.2f, 0.3f};
+ value = Value.toValue(floats2);
+ assertEquals(Type.array(Type.float32()), value.getType());
+ assertEquals(expectedFloatArray, value.getFloat32Array());
+
+ List expectedDoubleArray = Arrays.asList(0.1d, 0.2d, 0.3d, 0.4d);
+ Double[] doubles1 = {0.1d, 0.2d, 0.3d, 0.4d};
+ value = Value.toValue(doubles1);
+ assertEquals(Type.array(Type.float64()), value.getType());
+ assertEquals(expectedDoubleArray, value.getFloat64Array());
+
+ double[] doubles2 = {0.1d, 0.2d, 0.3d, 0.4d};
+ value = Value.toValue(doubles2);
+ assertEquals(Type.array(Type.float64()), value.getType());
+ assertEquals(expectedDoubleArray, value.getFloat64Array());
+
+ List expectedIntLongArray = Arrays.asList("1", "2", "3");
+ int[] ints1 = {1, 2, 3};
+ value = Value.toValue(ints1);
+ assertNull(value.getType());
+ assertEquals(expectedIntLongArray, value.getAsStringList());
+
+ Integer[] ints2 = {1, 2, 3};
+ value = Value.toValue(ints2);
+ assertNull(value.getType());
+ assertEquals(expectedIntLongArray, value.getAsStringList());
+
+ Long[] longs1 = {1L, 2L, 3L};
+ value = Value.toValue(longs1);
+ assertNull(value.getType());
+ assertEquals(expectedIntLongArray, value.getAsStringList());
+
+ long[] longs2 = {1L, 2L, 3L};
+ value = Value.toValue(longs2);
+ assertNull(value.getType());
+ assertEquals(expectedIntLongArray, value.getAsStringList());
+
+ String string = "hello";
+ value = Value.toValue(string);
+ assertNull(value.getType());
+ assertEquals("hello", value.getAsString());
+ }
+
+ @Test
+ public void testToValueIterable() {
+ List booleans = Arrays.asList(true, false);
+ Value value = Value.toValue(booleans);
+ assertEquals(Type.array(Type.bool()), value.getType());
+ assertEquals(booleans, value.getBoolArray());
+
+ List ints = Arrays.asList(1, 2, 3);
+ value = Value.toValue(ints);
+ assertNull(value.getType());
+ assertEquals(Arrays.asList("1", "2", "3"), value.getAsStringList());
+
+ List longs = Arrays.asList(1L, 2L, 3L);
+ value = Value.toValue(longs);
+ assertNull(value.getType());
+ assertEquals(Arrays.asList("1", "2", "3"), value.getAsStringList());
+
+ Set floats = new HashSet<>(Arrays.asList(0.1f, 0.2f, 0.3f));
+ value = Value.toValue(floats);
+ assertEquals(Type.array(Type.float32()), value.getType());
+ assertEquals(Arrays.asList(0.1f, 0.2f, 0.3f), value.getFloat32Array());
+
+ List doubles = Arrays.asList(0.1d, 0.2d, 0.3d, 0.4d);
+ value = Value.toValue(doubles);
+ assertEquals(Type.array(Type.float64()), value.getType());
+ assertEquals(doubles, value.getFloat64Array());
+
+ List bigDecimals =
+ Arrays.asList(BigDecimal.valueOf(0.1d), BigDecimal.valueOf(0.2d));
+ value = Value.toValue(bigDecimals);
+ assertEquals(Type.array(Type.numeric()), value.getType());
+ assertEquals(bigDecimals, value.getNumericArray());
+
+ List byteArrays =
+ Arrays.asList(ByteArray.copyFrom("hello"), ByteArray.copyFrom("world"));
+ value = Value.toValue(byteArrays);
+ assertEquals(Type.array(Type.bytes()), value.getType());
+ assertEquals(byteArrays, value.getBytesArray());
+
+ List bytes = Arrays.asList("hello".getBytes(), "world".getBytes());
+ value = Value.toValue(bytes);
+ assertEquals(Type.array(Type.bytes()), value.getType());
+ assertEquals(byteArrays, value.getBytesArray());
+
+ List intervals = Arrays.asList(Interval.ofDays(10), Interval.ofDays(20));
+ value = Value.toValue(intervals);
+ assertEquals(Type.array(Type.interval()), value.getType());
+ assertEquals(intervals, value.getIntervalArray());
+
+ List timestamps = Arrays.asList(Timestamp.now(), Timestamp.now());
+ value = Value.toValue(timestamps);
+ assertEquals(Type.array(Type.timestamp()), value.getType());
+ assertEquals(timestamps, value.getTimestampArray());
+
+ List dates =
+ Arrays.asList(Date.fromYearMonthDay(2024, 8, 23), Date.fromYearMonthDay(2024, 12, 27));
+ value = Value.toValue(dates);
+ assertEquals(Type.array(Type.date()), value.getType());
+ assertEquals(dates, value.getDateArray());
+
+ List localDates =
+ Arrays.asList(LocalDate.of(2024, 8, 23), LocalDate.of(2024, 12, 27));
+ value = Value.toValue(localDates);
+ assertEquals(Type.array(Type.date()), value.getType());
+ assertEquals(dates, value.getDateArray());
+
+ TimeZone defaultTimezone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("Asia/Kolkata"));
+ List localDateTimes =
+ Arrays.asList(
+ LocalDateTime.of(2024, 8, 23, 1, 49, 52, 10),
+ LocalDateTime.of(2024, 12, 27, 1, 49, 52, 10));
+ value = Value.toValue(localDateTimes);
+ assertNull(value.getType());
+ assertEquals(
+ Arrays.asList("2024-08-22T20:19:52.000Z", "2024-12-26T20:19:52.000Z"),
+ value.getAsStringList());
+ TimeZone.setDefault(defaultTimezone);
+
+ List offsetDateTimes =
+ Arrays.asList(
+ LocalDateTime.of(2024, 8, 23, 1, 49, 52, 10).atOffset(ZoneOffset.ofHours(1)),
+ LocalDateTime.of(2024, 12, 27, 1, 49, 52, 10).atOffset(ZoneOffset.ofHours(1)));
+ value = Value.toValue(offsetDateTimes);
+ assertNull(value.getType());
+ assertEquals(
+ Arrays.asList("2024-08-23T00:49:52.000Z", "2024-12-27T00:49:52.000Z"),
+ value.getAsStringList());
+
+ List zonedDateTimes =
+ Arrays.asList(
+ LocalDateTime.of(2024, 8, 23, 1, 49, 52, 10).atZone(ZoneId.of("UTC")),
+ LocalDateTime.of(2024, 12, 27, 1, 49, 52, 10).atZone(ZoneId.of("UTC")));
+ value = Value.toValue(zonedDateTimes);
+ assertNull(value.getType());
+ assertEquals(
+ Arrays.asList("2024-08-23T01:49:52.000Z", "2024-12-27T01:49:52.000Z"),
+ value.getAsStringList());
+ }
+
private static class BrokenSerializationList extends ForwardingList
implements Serializable {
private static final long serialVersionUID = 1L;