From 7792c9085a6e4ce1fb9fe2f8df4279f30539d87e Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Sun, 25 Apr 2021 13:26:51 +1000 Subject: [PATCH] feat: adds getValue to ResultSet (#1073) * feat: adds getValue method to result set Adds a generic getValue method to the result set. It can be used to retrieve any type from the database. * feat: removes custom int64 and float64 arrays * feat: fixes clirr checks Clirr does not support java 8 default implementations, so it thinks this are breaking changes. These in fact are not breaking changes, since we provide implementations in the interfaces. * test: fixes GrpcResultSet serialize test * feat: implements getValueInternal in result set Adds a less intrusive implementation of getValueInternal for the AbstractResultSet. * fix: fixes struct value decoding Uses the correct type for decoding structs from the result set * feat: makes the structArray method public Makes it public the method to retrieve an array of structs from a Value * test: adds tests for value literals * feat: accepts null in arrays for getValue * feat: allows nulls in values from result sets * docs: fixes java doc for getValue --- .../clirr-ignored-differences.xml | 13 + .../cloud/spanner/AbstractResultSet.java | 62 +++ .../cloud/spanner/AbstractStructReader.java | 16 + .../cloud/spanner/ForwardingStructReader.java | 12 + .../com/google/cloud/spanner/ResultSets.java | 10 + .../java/com/google/cloud/spanner/Struct.java | 5 + .../google/cloud/spanner/StructReader.java | 10 + .../java/com/google/cloud/spanner/Value.java | 2 +- .../connection/DirectExecuteResultSet.java | 13 + .../ReplaceableForwardingResultSet.java | 13 + .../AbstractStructReaderTypesTest.java | 78 ++- .../google/cloud/spanner/ResultSetsTest.java | 42 +- .../cloud/spanner/it/ITResultSetGetValue.java | 447 ++++++++++++++++++ 13 files changed, 700 insertions(+), 23 deletions(-) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITResultSetGetValue.java diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 641389cda9..c6f4c1f3a9 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -592,4 +592,17 @@ com/google/cloud/spanner/AsyncTransactionManager$CommitTimestampFuture java.lang.Object get() + + + + + 7012 + com/google/cloud/spanner/StructReader + com.google.cloud.spanner.Value getValue(int) + + + 7012 + com/google/cloud/spanner/StructReader + com.google.cloud.spanner.Value getValue(java.lang.String) + diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java index 00391447d9..6dd8d485ba 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java @@ -28,6 +28,7 @@ import com.google.cloud.ByteArray; import com.google.cloud.Date; import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Type.StructField; import com.google.cloud.spanner.spi.v1.SpannerRpc; import com.google.cloud.spanner.v1.stub.SpannerStubSettings; import com.google.common.annotations.VisibleForTesting; @@ -679,6 +680,62 @@ protected Date getDateInternal(int columnIndex) { return (Date) rowData.get(columnIndex); } + @Override + protected Value getValueInternal(int columnIndex) { + final List structFields = getType().getStructFields(); + final StructField structField = structFields.get(columnIndex); + final Type columnType = structField.getType(); + final boolean isNull = rowData.get(columnIndex) == null; + switch (columnType.getCode()) { + case BOOL: + return Value.bool(isNull ? null : getBooleanInternal(columnIndex)); + case INT64: + return Value.int64(isNull ? null : getLongInternal(columnIndex)); + case NUMERIC: + return Value.numeric(isNull ? null : getBigDecimalInternal(columnIndex)); + case FLOAT64: + return Value.float64(isNull ? null : getDoubleInternal(columnIndex)); + case STRING: + return Value.string(isNull ? null : getStringInternal(columnIndex)); + case BYTES: + return Value.bytes(isNull ? null : getBytesInternal(columnIndex)); + case TIMESTAMP: + return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex)); + case DATE: + return Value.date(isNull ? null : getDateInternal(columnIndex)); + case STRUCT: + return Value.struct(isNull ? null : getStructInternal(columnIndex)); + case ARRAY: + switch (columnType.getArrayElementType().getCode()) { + case BOOL: + return Value.boolArray(isNull ? null : getBooleanListInternal(columnIndex)); + case INT64: + return Value.int64Array(isNull ? null : getLongListInternal(columnIndex)); + case NUMERIC: + return Value.numericArray(isNull ? null : getBigDecimalListInternal(columnIndex)); + case FLOAT64: + return Value.float64Array(isNull ? null : getDoubleListInternal(columnIndex)); + case STRING: + return Value.stringArray(isNull ? null : getStringListInternal(columnIndex)); + case BYTES: + return Value.bytesArray(isNull ? null : getBytesListInternal(columnIndex)); + case TIMESTAMP: + return Value.timestampArray(isNull ? null : getTimestampListInternal(columnIndex)); + case DATE: + return Value.dateArray(isNull ? null : getDateListInternal(columnIndex)); + case STRUCT: + return Value.structArray( + columnType.getArrayElementType(), + isNull ? null : getStructListInternal(columnIndex)); + default: + throw new IllegalArgumentException( + "Invalid array value type " + this.type.getArrayElementType()); + } + default: + throw new IllegalArgumentException("Invalid value type " + this.type); + } + } + @Override protected Struct getStructInternal(int columnIndex) { return (Struct) rowData.get(columnIndex); @@ -1280,6 +1337,11 @@ protected Date getDateInternal(int columnIndex) { return currRow().getDateInternal(columnIndex); } + @Override + protected Value getValueInternal(int columnIndex) { + return currRow().getValueInternal(columnIndex); + } + @Override protected boolean[] getBooleanArrayInternal(int columnIndex) { return currRow().getBooleanArrayInternal(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java index 75e683e2e1..9b3f1810eb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java @@ -49,6 +49,10 @@ public abstract class AbstractStructReader implements StructReader { protected abstract Date getDateInternal(int columnIndex); + protected Value getValueInternal(int columnIndex) { + throw new UnsupportedOperationException("method should be overwritten"); + } + protected abstract boolean[] getBooleanArrayInternal(int columnIndex); protected abstract List getBooleanListInternal(int columnIndex); @@ -197,6 +201,18 @@ public Date getDate(String columnName) { return getDateInternal(columnIndex); } + @Override + public Value getValue(int columnIndex) { + checkNonNull(columnIndex, columnIndex); + return getValueInternal(columnIndex); + } + + @Override + public Value getValue(String columnName) { + int columnIndex = getColumnIndex(columnName); + return getValueInternal(columnIndex); + } + @Override public boolean[] getBooleanArray(int columnIndex) { checkNonNullOfType(columnIndex, Type.array(Type.bool()), columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java index f7e49daafd..d5f9488178 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java @@ -333,4 +333,16 @@ public List getStructList(String columnName) { checkValidState(); return delegate.get().getStructList(columnName); } + + @Override + public Value getValue(int columnIndex) { + checkValidState(); + return delegate.get().getValue(columnIndex); + } + + @Override + public Value getValue(String columnName) { + checkValidState(); + return delegate.get().getValue(columnName); + } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java index 5ec54960e6..245e585ceb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java @@ -273,6 +273,16 @@ public Date getDate(String columnName) { return getCurrentRowAsStruct().getDate(columnName); } + @Override + public Value getValue(int columnIndex) { + return getCurrentRowAsStruct().getValue(columnIndex); + } + + @Override + public Value getValue(String columnName) { + return getCurrentRowAsStruct().getValue(columnName); + } + @Override public boolean[] getBooleanArray(int columnIndex) { return getCurrentRowAsStruct().getBooleanArray(columnIndex); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java index 7982618948..3a7ddc8d99 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java @@ -207,6 +207,11 @@ protected Date getDateInternal(int columnIndex) { return values.get(columnIndex).getDate(); } + @Override + protected Value getValueInternal(int columnIndex) { + return values.get(columnIndex); + } + @Override protected Struct getStructInternal(int columnIndex) { return values.get(columnIndex).getStruct(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java index 33cffeed35..7b02f7078b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java @@ -132,6 +132,16 @@ public interface StructReader { /** Returns the value of a non-{@code NULL} column with type {@link Type#date()}. */ Date getDate(String columnName); + /** Returns the value of a nullable column as a {@link Value}. */ + default Value getValue(int columnIndex) { + throw new UnsupportedOperationException("method should be overwritten"); + } + + /** Returns the value of a nullable column as a {@link Value}. */ + default Value getValue(String columnName) { + throw new UnsupportedOperationException("method should be overwritten"); + } + /** * Returns the value of a non-{@code NULL} column with type {@code Type.array(Type.bool())}. * 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 fa6953b81b..9b336502c2 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 @@ -543,7 +543,7 @@ private Value() {} * * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type */ - abstract List getStructArray(); + public abstract List getStructArray(); @Override public String toString() { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java index 69782517d9..f23a1fca1b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java @@ -23,6 +23,7 @@ import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Value; import com.google.common.base.Preconditions; import com.google.spanner.v1.ResultSetStats; import java.math.BigDecimal; @@ -231,6 +232,18 @@ public Date getDate(String columnName) { return delegate.getDate(columnName); } + @Override + public Value getValue(int columnIndex) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getValue(columnIndex); + } + + @Override + public Value getValue(String columnName) { + Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); + return delegate.getValue(columnName); + } + @Override public boolean[] getBooleanArray(int columnIndex) { Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java index 2bd4f947cc..fac9e40f11 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java @@ -25,6 +25,7 @@ import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Value; import com.google.common.base.Preconditions; import com.google.spanner.v1.ResultSetStats; import java.math.BigDecimal; @@ -231,6 +232,18 @@ public Date getDate(String columnName) { return delegate.getDate(columnName); } + @Override + public Value getValue(int columnIndex) { + checkClosed(); + return delegate.getValue(columnIndex); + } + + @Override + public Value getValue(String columnName) { + checkClosed(); + return delegate.getValue(columnName); + } + @Override public boolean[] getBooleanArray(int columnIndex) { checkClosed(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java index 49154980d4..5873d466d3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java @@ -32,6 +32,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import javax.annotation.Nullable; import org.junit.Before; @@ -84,6 +85,11 @@ protected Date getDateInternal(int columnIndex) { return null; } + @Override + protected Value getValueInternal(int columnIndex) { + return null; + } + @Override protected boolean[] getBooleanArrayInternal(int columnIndex) { return null; @@ -162,81 +168,111 @@ public boolean isNull(int columnIndex) { public static Collection parameters() { return Arrays.asList( new Object[][] { - {Type.bool(), "getBooleanInternal", false, "getBoolean", null}, - {Type.int64(), "getLongInternal", 123L, "getLong", null}, - {Type.float64(), "getDoubleInternal", 2.0, "getDouble", null}, + { + Type.bool(), + "getBooleanInternal", + false, + "getBoolean", + Collections.singletonList("getValue") + }, + {Type.int64(), "getLongInternal", 123L, "getLong", Collections.singletonList("getValue")}, + { + Type.float64(), + "getDoubleInternal", + 2.0, + "getDouble", + Collections.singletonList("getValue") + }, { Type.numeric(), "getBigDecimalInternal", BigDecimal.valueOf(21, 1), "getBigDecimal", - null + Collections.singletonList("getValue") + }, + { + Type.string(), + "getStringInternal", + "a", + "getString", + Collections.singletonList("getValue") + }, + { + Type.bytes(), + "getBytesInternal", + ByteArray.copyFrom(new byte[] {0}), + "getBytes", + Collections.singletonList("getValue") }, - {Type.string(), "getStringInternal", "a", "getString", null}, - {Type.bytes(), "getBytesInternal", ByteArray.copyFrom(new byte[] {0}), "getBytes", null}, { Type.timestamp(), "getTimestampInternal", Timestamp.parseTimestamp("2015-09-15T00:00:00Z"), "getTimestamp", - null + Collections.singletonList("getValue") + }, + { + Type.date(), + "getDateInternal", + Date.parseDate("2015-09-15"), + "getDate", + Collections.singletonList("getValue") }, - {Type.date(), "getDateInternal", Date.parseDate("2015-09-15"), "getDate", null}, { Type.array(Type.bool()), "getBooleanArrayInternal", new boolean[] {true, false}, "getBooleanArray", - Arrays.asList("getBooleanList") + Arrays.asList("getBooleanList", "getValue") }, { Type.array(Type.bool()), "getBooleanListInternal", Arrays.asList(false, true), "getBooleanList", - Arrays.asList("getBooleanArray") + Arrays.asList("getBooleanArray", "getValue") }, { Type.array(Type.int64()), "getLongArrayInternal", new long[] {1, 2}, "getLongArray", - Arrays.asList("getLongList") + Arrays.asList("getLongList", "getValue") }, { Type.array(Type.int64()), "getLongListInternal", Arrays.asList(3L, 4L), "getLongList", - Arrays.asList("getLongArray") + Arrays.asList("getLongArray", "getValue") }, { Type.array(Type.float64()), "getDoubleArrayInternal", new double[] {1.0, 2.0}, "getDoubleArray", - Arrays.asList("getDoubleList") + Arrays.asList("getDoubleList", "getValue") }, { Type.array(Type.float64()), "getDoubleListInternal", Arrays.asList(2.0, 4.0), "getDoubleList", - Arrays.asList("getDoubleArray") + Arrays.asList("getDoubleArray", "getValue") }, { Type.array(Type.numeric()), "getBigDecimalListInternal", Arrays.asList(BigDecimal.valueOf(21, 1), BigDecimal.valueOf(41, 1)), "getBigDecimalList", - null + Collections.singletonList("getValue") }, { Type.array(Type.string()), "getStringListInternal", Arrays.asList("a", "b", "c"), "getStringList", - null + Collections.singletonList("getValue") }, { Type.array(Type.bytes()), @@ -244,7 +280,7 @@ public static Collection parameters() { Arrays.asList( ByteArray.copyFrom("a"), ByteArray.copyFrom("b"), ByteArray.copyFrom("c")), "getBytesList", - null + Collections.singletonList("getValue") }, { Type.array(Type.timestamp()), @@ -253,14 +289,14 @@ public static Collection parameters() { Timestamp.parseTimestamp("2015-09-15T00:00:00Z"), Timestamp.parseTimestamp("2015-09-14T00:00:00Z")), "getTimestampList", - null, + Collections.singletonList("getValue") }, { Type.array(Type.date()), "getDateListInternal", Arrays.asList(Date.parseDate("2015-09-15"), Date.parseDate("2015-09-14")), "getDateList", - null, + Collections.singletonList("getValue") }, { Type.array(Type.struct(StructField.of("f1", Type.int64()))), @@ -270,7 +306,7 @@ public static Collection parameters() { Struct.newBuilder().set("f1").to(2).build(), Struct.newBuilder().set("f1").to(3).build()), "getStructList", - null + Collections.singletonList("getValue") } }); } @@ -374,7 +410,7 @@ public void getterForIncorrectType() { } try { getterByIndex(method.getName(), columnIndex); - fail("Expected ISE for " + method); + fail("Expected " + IllegalStateException.class.getSimpleName() + " for " + method); } catch (IllegalStateException e) { assertWithMessage("Exception for " + method).that(e.getMessage()).contains("was " + type); assertWithMessage("Exception for " + method) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java index 2d7d695ea2..84a3793bfd 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java @@ -34,6 +34,7 @@ import com.google.common.util.concurrent.MoreExecutors; import java.math.BigDecimal; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -196,46 +197,81 @@ public void resultSetIteration() { assertThat(rs.getColumnType(2)).isEqualTo(Type.bool()); assertThat(rs.getCurrentRowAsStruct()).isEqualTo(struct1); assertThat(rs.getString(0)).isEqualTo("x"); + assertThat(rs.getValue(0)).isEqualTo(Value.string("x")); assertThat(rs.getLong(1)).isEqualTo(2L); + assertThat(rs.getValue(1)).isEqualTo(Value.int64(2L)); assertThat(rs.getBoolean(2)).isTrue(); + assertThat(rs.getValue(2)).isEqualTo(Value.bool(true)); assertThat(rs.getBoolean("f3")).isTrue(); + assertThat(rs.getValue("f3")).isEqualTo(Value.bool(true)); assertThat(rs.getDouble("doubleVal")).isWithin(0.0).of(doubleVal); + assertThat(rs.getValue("doubleVal").getFloat64()).isWithin(0.0).of(doubleVal); assertThat(rs.getDouble(3)).isWithin(0.0).of(doubleVal); + assertThat(rs.getValue(3).getFloat64()).isWithin(0.0).of(doubleVal); assertThat(rs.getBigDecimal("bigDecimalVal")).isEqualTo(new BigDecimal("1.23")); + assertThat(rs.getValue("bigDecimalVal")).isEqualTo(Value.numeric(new BigDecimal("1.23"))); assertThat(rs.getBigDecimal(4)).isEqualTo(new BigDecimal("1.23")); + assertThat(rs.getValue(4)).isEqualTo(Value.numeric(new BigDecimal("1.23"))); assertThat(rs.getString(5)).isEqualTo(stringVal); + assertThat(rs.getValue(5)).isEqualTo(Value.string(stringVal)); assertThat(rs.getString("stringVal")).isEqualTo(stringVal); + assertThat(rs.getValue("stringVal")).isEqualTo(Value.string(stringVal)); assertThat(rs.getBytes(6)).isEqualTo(ByteArray.copyFrom(byteVal)); + assertThat(rs.getValue(6)).isEqualTo(Value.bytes(ByteArray.copyFrom(byteVal))); assertThat(rs.getBytes("byteVal")).isEqualTo(ByteArray.copyFrom(byteVal)); + assertThat(rs.getValue("byteVal")).isEqualTo(Value.bytes(ByteArray.copyFrom(byteVal))); assertThat(rs.getTimestamp(7)).isEqualTo(Timestamp.ofTimeMicroseconds(usecs)); + assertThat(rs.getValue(7)).isEqualTo(Value.timestamp(Timestamp.ofTimeMicroseconds(usecs))); assertThat(rs.getTimestamp("timestamp")).isEqualTo(Timestamp.ofTimeMicroseconds(usecs)); + assertThat(rs.getValue("timestamp")) + .isEqualTo(Value.timestamp(Timestamp.ofTimeMicroseconds(usecs))); assertThat(rs.getDate(8)).isEqualTo(Date.fromYearMonthDay(year, month, day)); + assertThat(rs.getValue(8)).isEqualTo(Value.date(Date.fromYearMonthDay(year, month, day))); assertThat(rs.getDate("date")).isEqualTo(Date.fromYearMonthDay(year, month, day)); + assertThat(rs.getValue("date")).isEqualTo(Value.date(Date.fromYearMonthDay(year, month, day))); assertThat(rs.getBooleanArray(9)).isEqualTo(boolArray); + assertThat(rs.getValue(9)).isEqualTo(Value.boolArray(boolArray)); assertThat(rs.getBooleanArray("boolArray")).isEqualTo(boolArray); + assertThat(rs.getValue("boolArray")).isEqualTo(Value.boolArray(boolArray)); assertThat(rs.getBooleanList(9)).isEqualTo(Booleans.asList(boolArray)); assertThat(rs.getBooleanList("boolArray")).isEqualTo(Booleans.asList(boolArray)); assertThat(rs.getLongArray(10)).isEqualTo(longArray); + assertThat(rs.getValue(10)).isEqualTo(Value.int64Array(longArray)); assertThat(rs.getLongArray("longArray")).isEqualTo(longArray); + assertThat(rs.getValue("longArray")).isEqualTo(Value.int64Array(longArray)); assertThat(rs.getLongList(10)).isEqualTo(Longs.asList(longArray)); assertThat(rs.getLongList("longArray")).isEqualTo(Longs.asList(longArray)); assertThat(rs.getDoubleArray(11)).usingTolerance(0.0).containsAtLeast(doubleArray); + assertThat(rs.getValue(11)).isEqualTo(Value.float64Array(doubleArray)); assertThat(rs.getDoubleArray("doubleArray")) .usingTolerance(0.0) .containsExactly(doubleArray) .inOrder(); + assertThat(rs.getValue("doubleArray")).isEqualTo(Value.float64Array(doubleArray)); assertThat(rs.getDoubleList(11)).isEqualTo(Doubles.asList(doubleArray)); assertThat(rs.getDoubleList("doubleArray")).isEqualTo(Doubles.asList(doubleArray)); assertThat(rs.getBigDecimalList(12)).isEqualTo(Arrays.asList(bigDecimalArray)); + assertThat(rs.getValue(12)).isEqualTo(Value.numericArray(Arrays.asList(bigDecimalArray))); assertThat(rs.getBigDecimalList("bigDecimalArray")).isEqualTo(Arrays.asList(bigDecimalArray)); + assertThat(rs.getValue("bigDecimalArray")) + .isEqualTo(Value.numericArray(Arrays.asList(bigDecimalArray))); assertThat(rs.getBytesList(13)).isEqualTo(Arrays.asList(byteArray)); + assertThat(rs.getValue(13)).isEqualTo(Value.bytesArray(Arrays.asList(byteArray))); assertThat(rs.getBytesList("byteArray")).isEqualTo(Arrays.asList(byteArray)); + assertThat(rs.getValue("byteArray")).isEqualTo(Value.bytesArray(Arrays.asList(byteArray))); assertThat(rs.getTimestampList(14)).isEqualTo(Arrays.asList(timestampArray)); + assertThat(rs.getValue(14)).isEqualTo(Value.timestampArray(Arrays.asList(timestampArray))); assertThat(rs.getTimestampList("timestampArray")).isEqualTo(Arrays.asList(timestampArray)); + assertThat(rs.getValue("timestampArray")) + .isEqualTo(Value.timestampArray(Arrays.asList(timestampArray))); assertThat(rs.getDateList(15)).isEqualTo(Arrays.asList(dateArray)); + assertThat(rs.getValue(15)).isEqualTo(Value.dateArray(Arrays.asList(dateArray))); assertThat(rs.getDateList("dateArray")).isEqualTo(Arrays.asList(dateArray)); + assertThat(rs.getValue("dateArray")).isEqualTo(Value.dateArray(Arrays.asList(dateArray))); assertThat(rs.getStringList(16)).isEqualTo(Arrays.asList(stringArray)); + assertThat(rs.getValue(16)).isEqualTo(Value.stringArray(Arrays.asList(stringArray))); assertThat(rs.getStringList("stringArray")).isEqualTo(Arrays.asList(stringArray)); + assertThat(rs.getValue("stringArray")).isEqualTo(Value.stringArray(Arrays.asList(stringArray))); assertThat(rs.next()).isTrue(); assertThat(rs.getCurrentRowAsStruct()).isEqualTo(struct2); @@ -264,7 +300,7 @@ public void resultSetIterationWithStructColumns() { Struct struct1 = Struct.newBuilder().set("f1").to(value1).set("f2").to((Long) null).build(); try { - ResultSets.forRows(type, Arrays.asList(struct1)); + ResultSets.forRows(type, Collections.singletonList(struct1)); fail("Expected exception"); } catch (UnsupportedOperationException ex) { assertThat(ex.getMessage()) @@ -317,7 +353,9 @@ public void resultSetIterationWithArrayStructColumns() { assertThat(rs.getCurrentRowAsStruct()).isEqualTo(struct1); assertThat(rs.getStructList(0)).isEqualTo(arrayValue); + assertThat(rs.getValue(0)).isEqualTo(Value.structArray(nestedStructType, arrayValue)); assertThat(rs.getStructList("f1")).isEqualTo(arrayValue); + assertThat(rs.getValue("f1")).isEqualTo(Value.structArray(nestedStructType, arrayValue)); assertThat(rs.isNull(1)).isTrue(); assertThat(rs.next()).isTrue(); @@ -326,7 +364,9 @@ public void resultSetIterationWithArrayStructColumns() { assertThat(rs.isNull(0)).isTrue(); assertThat(rs.isNull("f1")).isTrue(); assertThat(rs.getLong(1)).isEqualTo(20); + assertThat(rs.getValue(1)).isEqualTo(Value.int64(20)); assertThat(rs.getLong("f2")).isEqualTo(20); + assertThat(rs.getValue("f2")).isEqualTo(Value.int64(20)); assertThat(rs.next()).isFalse(); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITResultSetGetValue.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITResultSetGetValue.java new file mode 100644 index 0000000000..0b2f19e99d --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITResultSetGetValue.java @@ -0,0 +1,447 @@ +/* + * Copyright 2021 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.it; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.ByteArray; +import com.google.cloud.Date; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Type.StructField; +import com.google.cloud.spanner.Value; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.common.primitives.Doubles; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITResultSetGetValue { + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + + // For floats / doubles comparison + private static final double DELTA = 1e-15; + private static final String TABLE_NAME = "TestTable"; + private static DatabaseClient databaseClient; + + @BeforeClass + public static void beforeClass() { + final RemoteSpannerHelper testHelper = env.getTestHelper(); + final Database database = + testHelper.createTestDatabase( + "CREATE TABLE " + + TABLE_NAME + + "(" + + "Id INT64 NOT NULL," + + "bool BOOL," + + "int64 INT64," + + "float64 FLOAT64," + + "numeric NUMERIC," + + "string STRING(MAX)," + + "bytes BYTES(MAX)," + + "timestamp TIMESTAMP," + + "date DATE," + + "boolArray ARRAY," + + "int64Array ARRAY," + + "float64Array ARRAY," + + "numericArray ARRAY," + + "stringArray ARRAY," + + "bytesArray ARRAY," + + "timestampArray ARRAY," + + "dateArray ARRAY" + + ") PRIMARY KEY (Id)"); + + databaseClient = testHelper.getDatabaseClient(database); + } + + @Test + public void testReadNonNullValues() { + databaseClient.write( + Collections.singletonList( + Mutation.newInsertBuilder(TABLE_NAME) + .set("Id") + .to(1L) + .set("bool") + .to(true) + .set("int64") + .to(10L) + .set("float64") + .to(20D) + .set("numeric") + .to(new BigDecimal("30")) + .set("string") + .to("stringValue") + .set("bytes") + .to(ByteArray.copyFrom("bytesValue")) + .set("timestamp") + .to(Timestamp.ofTimeSecondsAndNanos(1, 0)) + .set("date") + .to(Date.fromYearMonthDay(2021, 1, 2)) + .set("boolArray") + .toBoolArray(new boolean[] {false, true}) + .set("int64Array") + .toInt64Array(new long[] {100L, 200L}) + .set("float64Array") + .toFloat64Array(new double[] {1000D, 2000D}) + .set("numericArray") + .toNumericArray(Arrays.asList(new BigDecimal("10000"), new BigDecimal("20000"))) + .set("stringArray") + .toStringArray(Arrays.asList("string1", "string2")) + .set("bytesArray") + .toBytesArray( + Arrays.asList(ByteArray.copyFrom("bytes1"), ByteArray.copyFrom("bytes2"))) + .set("timestampArray") + .toTimestampArray( + Arrays.asList( + Timestamp.ofTimeSecondsAndNanos(10, 0), + Timestamp.ofTimeSecondsAndNanos(20, 0))) + .set("dateArray") + .toDateArray( + Arrays.asList( + Date.fromYearMonthDay(2021, 2, 3), Date.fromYearMonthDay(2021, 3, 4))) + .build())); + + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery(Statement.of("SELECT * FROM " + TABLE_NAME + " WHERE Id = 1"))) { + resultSet.next(); + + assertEquals(Value.int64(1L), resultSet.getValue("Id")); + assertEquals(Value.bool(true), resultSet.getValue("bool")); + assertEquals(Value.int64(10L), resultSet.getValue("int64")); + assertEquals(20D, resultSet.getValue("float64").getFloat64(), 1e-15); + assertEquals(Value.numeric(new BigDecimal("30")), resultSet.getValue("numeric")); + assertEquals(Value.string("stringValue"), resultSet.getValue("string")); + assertEquals(Value.bytes(ByteArray.copyFrom("bytesValue")), resultSet.getValue("bytes")); + assertEquals( + Value.timestamp(Timestamp.ofTimeSecondsAndNanos(1, 0)), resultSet.getValue("timestamp")); + assertEquals(Value.date(Date.fromYearMonthDay(2021, 1, 2)), resultSet.getValue("date")); + assertEquals(Value.boolArray(new boolean[] {false, true}), resultSet.getValue("boolArray")); + assertEquals(Value.int64Array(new long[] {100L, 200L}), resultSet.getValue("int64Array")); + assertArrayEquals( + new double[] {1000D, 2000D}, + Doubles.toArray(resultSet.getValue("float64Array").getFloat64Array()), + 1e-15); + assertEquals( + Value.numericArray(Arrays.asList(new BigDecimal("10000"), new BigDecimal("20000"))), + resultSet.getValue("numericArray")); + assertEquals( + Value.stringArray(Arrays.asList("string1", "string2")), + resultSet.getValue("stringArray")); + assertEquals( + Value.bytesArray( + Arrays.asList(ByteArray.copyFrom("bytes1"), ByteArray.copyFrom("bytes2"))), + resultSet.getValue("bytesArray")); + assertEquals( + Value.timestampArray( + Arrays.asList( + Timestamp.ofTimeSecondsAndNanos(10, 0), Timestamp.ofTimeSecondsAndNanos(20, 0))), + resultSet.getValue("timestampArray")); + assertEquals( + Value.dateArray( + Arrays.asList(Date.fromYearMonthDay(2021, 2, 3), Date.fromYearMonthDay(2021, 3, 4))), + resultSet.getValue("dateArray")); + } + } + + @Test + public void testReadNullValues() { + databaseClient.write( + Collections.singletonList(Mutation.newInsertBuilder(TABLE_NAME).set("Id").to(2L).build())); + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery(Statement.of("SELECT * FROM " + TABLE_NAME + " WHERE Id = 2"))) { + resultSet.next(); + + assertEquals(Value.int64(2L), resultSet.getValue("Id")); + assertTrue(resultSet.getValue("bool").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("bool").getBool()); + assertTrue(resultSet.getValue("int64").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("int64").getInt64()); + assertTrue(resultSet.getValue("float64").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("float64").getFloat64()); + assertTrue(resultSet.getValue("numeric").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("numeric").getNumeric()); + assertTrue(resultSet.getValue("string").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("string").getString()); + assertTrue(resultSet.getValue("bytes").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("bytes").getBytes()); + assertTrue(resultSet.getValue("timestamp").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("timestamp").getTimestamp()); + assertTrue(resultSet.getValue("date").isNull()); + assertThrows(IllegalStateException.class, () -> resultSet.getValue("date").getDate()); + assertTrue(resultSet.getValue("boolArray").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("boolArray").getBoolArray()); + assertTrue(resultSet.getValue("int64Array").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("int64Array").getInt64Array()); + assertTrue(resultSet.getValue("float64Array").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("float64Array").getFloat64Array()); + assertTrue(resultSet.getValue("numericArray").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("numericArray").getNumericArray()); + assertTrue(resultSet.getValue("stringArray").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("stringArray").getStringArray()); + assertTrue(resultSet.getValue("bytesArray").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("bytesArray").getBytesArray()); + assertTrue(resultSet.getValue("timestampArray").isNull()); + assertThrows( + IllegalStateException.class, + () -> resultSet.getValue("timestampArray").getTimestampArray()); + assertTrue(resultSet.getValue("dateArray").isNull()); + assertThrows( + IllegalStateException.class, () -> resultSet.getValue("dateArray").getDateArray()); + } + } + + @Test + public void testReadNullValuesInArrays() { + databaseClient.write( + Collections.singletonList( + Mutation.newInsertBuilder(TABLE_NAME) + .set("Id") + .to(3L) + .set("boolArray") + .toBoolArray(Arrays.asList(true, null)) + .set("int64Array") + .toInt64Array(Arrays.asList(null, 2L)) + .set("float64Array") + .toFloat64Array(Arrays.asList(null, 10D)) + .set("numericArray") + .toNumericArray(Arrays.asList(new BigDecimal("10000"), null)) + .set("stringArray") + .toStringArray(Arrays.asList(null, "string2")) + .set("bytesArray") + .toBytesArray(Arrays.asList(ByteArray.copyFrom("bytes1"), null)) + .set("timestampArray") + .toTimestampArray(Arrays.asList(null, Timestamp.ofTimeSecondsAndNanos(20, 0))) + .set("dateArray") + .toDateArray(Arrays.asList(Date.fromYearMonthDay(2021, 2, 3), null)) + .build())); + + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery(Statement.of("SELECT * FROM " + TABLE_NAME + " WHERE Id = 3"))) { + resultSet.next(); + + assertEquals(Value.int64(3L), resultSet.getValue("Id")); + assertEquals(Value.boolArray(Arrays.asList(true, null)), resultSet.getValue("boolArray")); + assertEquals(Value.int64Array(Arrays.asList(null, 2L)), resultSet.getValue("int64Array")); + assertNull(resultSet.getValue("float64Array").getFloat64Array().get(0)); + assertEquals(10D, resultSet.getValue("float64Array").getFloat64Array().get(1), DELTA); + assertEquals( + Value.numericArray(Arrays.asList(new BigDecimal("10000"), null)), + resultSet.getValue("numericArray")); + assertEquals( + Value.stringArray(Arrays.asList(null, "string2")), resultSet.getValue("stringArray")); + assertEquals( + Value.bytesArray(Arrays.asList(ByteArray.copyFrom("bytes1"), null)), + resultSet.getValue("bytesArray")); + assertEquals( + Value.timestampArray(Arrays.asList(null, Timestamp.ofTimeSecondsAndNanos(20, 0))), + resultSet.getValue("timestampArray")); + assertEquals( + Value.dateArray(Arrays.asList(Date.fromYearMonthDay(2021, 2, 3), null)), + resultSet.getValue("dateArray")); + } + } + + @Test + public void testReadNonFloat64Literals() { + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery( + Statement.of( + "SELECT " + + "TRUE AS bool," + + "1 AS int64," + + "CAST('100' AS NUMERIC) AS numeric," + + "'stringValue' AS string," + + "CAST('bytesValue' AS BYTES) AS bytes," + + "CAST('1970-01-01T00:00:01Z' AS TIMESTAMP) AS timestamp," + + "CAST('2021-02-03' AS DATE) AS date," + + "[false, true] AS boolArray," + + "[1, 2] AS int64Array," + + "[CAST('100' AS NUMERIC), CAST('200' AS NUMERIC)] AS numericArray," + + "['string1', 'string2'] AS stringArray," + + "[CAST('bytes1' AS BYTES), CAST('bytes2' AS BYTES)] AS bytesArray," + + "[CAST('1970-01-01T00:00:01.000000002Z' AS TIMESTAMP), CAST('1970-01-01T00:00:02.000000003Z' AS TIMESTAMP)] AS timestampArray," + + "[CAST('2020-01-02' AS DATE), CAST('2021-02-03' AS DATE)] AS dateArray," + + "ARRAY(SELECT STRUCT(" + + " TRUE AS structBool," + + " 1 AS structInt64," + + " CAST('100' AS NUMERIC) AS structNumeric," + + " 'stringValue' AS structString," + + " CAST('bytesValue' AS BYTES) AS structBytes," + + " CAST('1970-01-01T00:00:01Z' AS TIMESTAMP) AS structTimestamp," + + " CAST('2020-01-02' AS DATE) AS structDate," + + " [false, true] AS structBoolArray," + + " [1, 2] AS structInt64Array," + + " [CAST('100' AS NUMERIC), CAST('200' AS NUMERIC)] AS structNumericArray," + + " ['string1', 'string2'] AS structStringArray," + + " [CAST('bytes1' AS BYTES), CAST('bytes2' AS BYTES)] AS structBytesArray," + + " [CAST('1970-01-01T00:00:01.000000002Z' AS TIMESTAMP), CAST('1970-01-01T00:00:02.000000003Z' AS TIMESTAMP)] AS structTimestampArray," + + " [CAST('2020-01-02' AS DATE), CAST('2021-02-03' AS DATE)] AS structDateArray" + + ")) AS structArray"))) { + resultSet.next(); + + assertEquals(Value.bool(true), resultSet.getValue("bool")); + assertEquals(Value.int64(1L), resultSet.getValue("int64")); + assertEquals(Value.numeric(new BigDecimal("100")), resultSet.getValue("numeric")); + assertEquals(Value.string("stringValue"), resultSet.getValue("string")); + assertEquals(Value.bytes(ByteArray.copyFrom("bytesValue")), resultSet.getValue("bytes")); + assertEquals( + Value.timestamp(Timestamp.ofTimeSecondsAndNanos(1, 0)), resultSet.getValue("timestamp")); + assertEquals(Value.date(Date.fromYearMonthDay(2021, 2, 3)), resultSet.getValue("date")); + assertEquals(Value.boolArray(new boolean[] {false, true}), resultSet.getValue("boolArray")); + assertEquals(Value.int64Array(new long[] {1L, 2L}), resultSet.getValue("int64Array")); + assertEquals( + Value.numericArray(Arrays.asList(new BigDecimal("100"), new BigDecimal("200"))), + resultSet.getValue("numericArray")); + assertEquals( + Value.stringArray(Arrays.asList("string1", "string2")), + resultSet.getValue("stringArray")); + assertEquals( + Value.bytesArray( + Arrays.asList(ByteArray.copyFrom("bytes1"), ByteArray.copyFrom("bytes2"))), + resultSet.getValue("bytesArray")); + assertEquals( + Value.timestampArray( + Arrays.asList( + Timestamp.ofTimeSecondsAndNanos(1, 2), Timestamp.ofTimeSecondsAndNanos(2, 3))), + resultSet.getValue("timestampArray")); + assertEquals( + Value.dateArray( + Arrays.asList(Date.fromYearMonthDay(2020, 1, 2), Date.fromYearMonthDay(2021, 2, 3))), + resultSet.getValue("dateArray")); + assertEquals( + Value.structArray( + Type.struct( + StructField.of("structBool", Type.bool()), + StructField.of("structInt64", Type.int64()), + StructField.of("structNumeric", Type.numeric()), + StructField.of("structString", Type.string()), + StructField.of("structBytes", Type.bytes()), + StructField.of("structTimestamp", Type.timestamp()), + StructField.of("structDate", Type.date()), + StructField.of("structBoolArray", Type.array(Type.bool())), + StructField.of("structInt64Array", Type.array(Type.int64())), + StructField.of("structNumericArray", Type.array(Type.numeric())), + StructField.of("structStringArray", Type.array(Type.string())), + StructField.of("structBytesArray", Type.array(Type.bytes())), + StructField.of("structTimestampArray", Type.array(Type.timestamp())), + StructField.of("structDateArray", Type.array(Type.date()))), + Collections.singletonList( + Struct.newBuilder() + .set("structBool") + .to(Value.bool(true)) + .set("structInt64") + .to(Value.int64(1L)) + .set("structNumeric") + .to(new BigDecimal("100")) + .set("structString") + .to("stringValue") + .set("structBytes") + .to(ByteArray.copyFrom("bytesValue")) + .set("structTimestamp") + .to(Timestamp.ofTimeSecondsAndNanos(1, 0)) + .set("structDate") + .to(Date.fromYearMonthDay(2020, 1, 2)) + .set("structBoolArray") + .toBoolArray(new boolean[] {false, true}) + .set("structInt64Array") + .toInt64Array(new long[] {1L, 2L}) + .set("structNumericArray") + .toNumericArray(Arrays.asList(new BigDecimal("100"), new BigDecimal("200"))) + .set("structStringArray") + .toStringArray(Arrays.asList("string1", "string2")) + .set("structBytesArray") + .toBytesArray( + Arrays.asList(ByteArray.copyFrom("bytes1"), ByteArray.copyFrom("bytes2"))) + .set("structTimestampArray") + .toTimestampArray( + Arrays.asList( + Timestamp.ofTimeSecondsAndNanos(1, 2), + Timestamp.ofTimeSecondsAndNanos(2, 3))) + .set("structDateArray") + .toDateArray( + Arrays.asList( + Date.fromYearMonthDay(2020, 1, 2), Date.fromYearMonthDay(2021, 2, 3))) + .build())), + resultSet.getValue("structArray")); + } + } + + @Test + public void testReadFloat64Literals() { + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery( + Statement.of( + "SELECT " + + "10.0 AS float64," + + "[20.0, 30.0] AS float64Array," + + "ARRAY(SELECT STRUCT(" + + " 40.0 AS structFloat64," + + " [50.0, 60.0] AS structFloat64Array" + + ")) AS structArray"))) { + resultSet.next(); + + final Struct struct = resultSet.getValue("structArray").getStructArray().get(0); + + assertEquals(10D, resultSet.getValue("float64").getFloat64(), DELTA); + assertArrayEquals( + new double[] {20D, 30D}, + Doubles.toArray(resultSet.getValue("float64Array").getFloat64Array()), + DELTA); + assertEquals(40D, struct.getDouble("structFloat64"), DELTA); + assertArrayEquals( + new double[] {50D, 60D}, struct.getDoubleArray("structFloat64Array"), DELTA); + } + } +}