From 35859a96bf5fe1de4142e4ded895c3bbdb8f1c8f Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 18 Jan 2021 12:39:39 +0100 Subject: [PATCH 1/3] feat: add support for array as ResultSet --- .../google/cloud/spanner/jdbc/JdbcArray.java | 85 +++++++- .../cloud/spanner/jdbc/JdbcArrayTest.java | 186 ++++++++++++++++-- 2 files changed, 244 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java index 67d8e0c1..2a9d9e77 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java @@ -16,11 +16,21 @@ package com.google.cloud.spanner.jdbc; +import com.google.cloud.ByteArray; +import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Type.StructField; +import com.google.cloud.spanner.ValueBinder; +import com.google.common.collect.ImmutableList; import com.google.rpc.Code; +import java.math.BigDecimal; import java.sql.Array; +import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.Timestamp; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -137,28 +147,89 @@ public Object getArray(long index, int count, Map> map) throws return null; } - private static final String RESULTSET_NOT_SUPPORTED = - "Getting a ResultSet from an array is not supported"; + private static final String RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED = + "Getting a ResultSet with a custom type mapping from an array is not supported"; @Override public ResultSet getResultSet() throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + return getResultSet(1L, Integer.MAX_VALUE); } @Override public ResultSet getResultSet(Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + throw new SQLFeatureNotSupportedException(RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED); } @Override - public ResultSet getResultSet(long index, int count) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + public ResultSet getResultSet(long startIndex, int count) throws SQLException { + JdbcPreconditions.checkArgument( + startIndex + count - 1L <= Integer.MAX_VALUE, + String.format("End index cannot exceed %d", Integer.MAX_VALUE)); + JdbcPreconditions.checkArgument(startIndex >= 1L, "Start index must be >= 1"); + JdbcPreconditions.checkArgument(count >= 0, "Count must be >= 0"); + checkFree(); + ImmutableList.Builder rows = ImmutableList.builder(); + int added = 0; + if (data != null) { + // Note that array index in JDBC is base-one. + for (int index = (int) startIndex; + added < count && index <= ((Object[]) data).length; + index++) { + Object value = ((Object[]) data)[index - 1]; + ValueBinder binder = + Struct.newBuilder().set("INDEX").to(index).set("VALUE"); + Struct.Builder builder = null; + switch (type.getCode()) { + case BOOL: + builder = binder.to((Boolean) value); + break; + case BYTES: + builder = binder.to(ByteArray.copyFrom((byte[]) value)); + break; + case DATE: + builder = binder.to(JdbcTypeConverter.toGoogleDate((Date) value)); + break; + case FLOAT64: + builder = binder.to((Double) value); + break; + case INT64: + builder = binder.to((Long) value); + break; + case NUMERIC: + builder = binder.to((BigDecimal) value); + break; + case STRING: + builder = binder.to((String) value); + break; + case TIMESTAMP: + builder = binder.to(JdbcTypeConverter.toGoogleTimestamp((Timestamp) value)); + break; + case ARRAY: + case STRUCT: + default: + throw new SQLFeatureNotSupportedException( + String.format( + "Array of type %s cannot be converted to a ResultSet", type.getCode().name())); + } + rows.add(builder.build()); + added++; + if (added == count) { + break; + } + } + } + return JdbcResultSet.of( + ResultSets.forRows( + Type.struct( + StructField.of("INDEX", Type.int64()), + StructField.of("VALUE", type.getSpannerType())), + rows.build())); } @Override public ResultSet getResultSet(long index, int count, Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + throw new SQLFeatureNotSupportedException(RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED); } @Override diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java index 01184c2d..fc7bebdb 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -16,10 +16,16 @@ package com.google.cloud.spanner.jdbc; -import static org.junit.Assert.assertEquals; +import static com.google.cloud.spanner.jdbc.JdbcTypeConverter.toSqlDate; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl; import java.math.BigDecimal; import java.sql.Date; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; @@ -35,42 +41,182 @@ public void testCreateArrayTypeName() throws SQLException { // Note that JDBC array indices start at 1. JdbcArray array; array = JdbcArray.createArray("BOOL", new Boolean[] {true, false, true}); - assertEquals(array.getBaseType(), Types.BOOLEAN); - assertEquals(((Boolean[]) array.getArray(1, 1))[0], Boolean.TRUE); + assertThat(array.getBaseType()).isEqualTo(Types.BOOLEAN); + assertThat(((Boolean[]) array.getArray(1, 1))[0]).isEqualTo(Boolean.TRUE); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(true); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(false); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(true); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("BYTES", new byte[][] {new byte[] {1, 2}, new byte[] {3, 4}}); - assertEquals(array.getBaseType(), Types.BINARY); - assertEquals(((byte[][]) array.getArray(1, 1))[0][1], (byte) 2); + assertThat(array.getBaseType()).isEqualTo(Types.BINARY); + assertThat(((byte[][]) array.getArray(1, 1))[0][1]).isEqualTo((byte) 2); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(2)).isEqualTo(new byte[] {1, 2}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(2)).isEqualTo(new byte[] {3, 4}); + assertThat(rs.next()).isFalse(); + } array = - JdbcArray.createArray("DATE", new Date[] {new Date(1L), new Date(100L), new Date(1000L)}); - assertEquals(array.getBaseType(), Types.DATE); - assertEquals(((Date[]) array.getArray(1, 1))[0], new Date(1L)); + JdbcArray.createArray( + "DATE", + new Date[] { + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18)), + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2000, 2, 29)), + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2019, 8, 31)) + }); + assertThat(array.getBaseType()).isEqualTo(Types.DATE); + assertThat(((Date[]) array.getArray(1, 1))[0]) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18))); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18))); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2000, 2, 29))); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2019, 8, 31))); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("FLOAT64", new Double[] {1.1D, 2.2D, Math.PI}); - assertEquals(array.getBaseType(), Types.DOUBLE); - assertEquals(((Double[]) array.getArray(1, 3))[2], Double.valueOf(Math.PI)); + assertThat(array.getBaseType()).isEqualTo(Types.DOUBLE); + assertThat(((Double[]) array.getArray(1, 3))[2]).isEqualTo(Double.valueOf(Math.PI)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(1.1D); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(2.2D); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(Math.PI); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("INT64", new Long[] {1L, 2L, 3L}); - assertEquals(array.getBaseType(), Types.BIGINT); - assertEquals(((Long[]) array.getArray(1, 1))[0], Long.valueOf(1L)); + assertThat(array.getBaseType()).isEqualTo(Types.BIGINT); + assertThat(((Long[]) array.getArray(1, 1))[0]).isEqualTo(Long.valueOf(1L)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(1L); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(2L); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(3L); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("NUMERIC", new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN}); - assertEquals(array.getBaseType(), Types.NUMERIC); - assertEquals(((BigDecimal[]) array.getArray(1, 1))[0], BigDecimal.ONE); - assertEquals(((BigDecimal[]) array.getArray(2, 1))[0], null); - assertEquals(((BigDecimal[]) array.getArray(3, 1))[0], BigDecimal.TEN); + assertThat(array.getBaseType()).isEqualTo(Types.NUMERIC); + assertThat(((BigDecimal[]) array.getArray(1, 1))[0]).isEqualTo(BigDecimal.ONE); + assertThat(((BigDecimal[]) array.getArray(2, 1))[0]).isNull(); + assertThat(((BigDecimal[]) array.getArray(3, 1))[0]).isEqualTo(BigDecimal.TEN); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isEqualTo(BigDecimal.ONE); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isEqualTo(BigDecimal.TEN); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); - assertEquals(array.getBaseType(), Types.NVARCHAR); - assertEquals(((String[]) array.getArray(1, 1))[0], "foo"); + assertThat(array.getBaseType()).isEqualTo(Types.NVARCHAR); + assertThat(((String[]) array.getArray(1, 1))[0]).isEqualTo("foo"); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("foo"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("bar"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("baz"); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray( "TIMESTAMP", new Timestamp[] {new Timestamp(1L), new Timestamp(100L), new Timestamp(1000L)}); - assertEquals(array.getBaseType(), Types.TIMESTAMP); - assertEquals(((Timestamp[]) array.getArray(1, 1))[0], new Timestamp(1L)); + assertThat(array.getBaseType()).isEqualTo(Types.TIMESTAMP); + assertThat(((Timestamp[]) array.getArray(1, 1))[0]).isEqualTo(new Timestamp(1L)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(1L)); + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(100L)); + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(1000L)); + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void testGetResultSetMetadata() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet()) { + ResultSetMetaData metadata = rs.getMetaData(); + assertThat(metadata.getColumnCount()).isEqualTo(2); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.BIGINT); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.NVARCHAR); + assertThat(metadata.getColumnName(1)).isEqualTo("INDEX"); + assertThat(metadata.getColumnName(2)).isEqualTo("VALUE"); + } + } + + @Test + public void testGetResultSetWithIndex() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(2L, 1)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong("INDEX")).isEqualTo(2L); + assertThat(rs.getString("VALUE")).isEqualTo("bar"); + assertThat(rs.next()).isFalse(); + } + + try (ResultSet rs = array.getResultSet(1L, 5)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("foo"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("bar"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("baz"); + assertThat(rs.next()).isFalse(); + } + + try (ResultSet rs = array.getResultSet(1L, 0)) { + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void testGetResultSetWithInvalidIndex() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(0L, 1)) { + fail("missing expected exception"); + } catch (JdbcSqlExceptionImpl e) { + assertThat(e.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } + } + + @Test + public void testGetResultSetWithInvalidCount() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(1L, -1)) { + fail("missing expected exception"); + } catch (JdbcSqlExceptionImpl e) { + assertThat(e.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } } } From 066ae3f55a86b6b7c69f84c57a03bccb7999cbda Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 18 Jan 2021 12:39:39 +0100 Subject: [PATCH 2/3] feat: add support for array as ResultSet JDBC arrays can optionally be converted to ResultSets. This feature was not implemented for the Cloud Spanner JDBC driver. This would cause DBeaver to show an error message instead of the actual data when fetching a NUMERIC array. Other arrays would be fetched correctly by DBeaver, as those array types do not use the conversion to a ResultSet. --- .../google/cloud/spanner/jdbc/JdbcArray.java | 85 +++++++- .../cloud/spanner/jdbc/JdbcArrayTest.java | 186 ++++++++++++++++-- 2 files changed, 244 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java index 67d8e0c1..2a9d9e77 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java @@ -16,11 +16,21 @@ package com.google.cloud.spanner.jdbc; +import com.google.cloud.ByteArray; +import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Type.StructField; +import com.google.cloud.spanner.ValueBinder; +import com.google.common.collect.ImmutableList; import com.google.rpc.Code; +import java.math.BigDecimal; import java.sql.Array; +import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.Timestamp; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -137,28 +147,89 @@ public Object getArray(long index, int count, Map> map) throws return null; } - private static final String RESULTSET_NOT_SUPPORTED = - "Getting a ResultSet from an array is not supported"; + private static final String RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED = + "Getting a ResultSet with a custom type mapping from an array is not supported"; @Override public ResultSet getResultSet() throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + return getResultSet(1L, Integer.MAX_VALUE); } @Override public ResultSet getResultSet(Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + throw new SQLFeatureNotSupportedException(RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED); } @Override - public ResultSet getResultSet(long index, int count) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + public ResultSet getResultSet(long startIndex, int count) throws SQLException { + JdbcPreconditions.checkArgument( + startIndex + count - 1L <= Integer.MAX_VALUE, + String.format("End index cannot exceed %d", Integer.MAX_VALUE)); + JdbcPreconditions.checkArgument(startIndex >= 1L, "Start index must be >= 1"); + JdbcPreconditions.checkArgument(count >= 0, "Count must be >= 0"); + checkFree(); + ImmutableList.Builder rows = ImmutableList.builder(); + int added = 0; + if (data != null) { + // Note that array index in JDBC is base-one. + for (int index = (int) startIndex; + added < count && index <= ((Object[]) data).length; + index++) { + Object value = ((Object[]) data)[index - 1]; + ValueBinder binder = + Struct.newBuilder().set("INDEX").to(index).set("VALUE"); + Struct.Builder builder = null; + switch (type.getCode()) { + case BOOL: + builder = binder.to((Boolean) value); + break; + case BYTES: + builder = binder.to(ByteArray.copyFrom((byte[]) value)); + break; + case DATE: + builder = binder.to(JdbcTypeConverter.toGoogleDate((Date) value)); + break; + case FLOAT64: + builder = binder.to((Double) value); + break; + case INT64: + builder = binder.to((Long) value); + break; + case NUMERIC: + builder = binder.to((BigDecimal) value); + break; + case STRING: + builder = binder.to((String) value); + break; + case TIMESTAMP: + builder = binder.to(JdbcTypeConverter.toGoogleTimestamp((Timestamp) value)); + break; + case ARRAY: + case STRUCT: + default: + throw new SQLFeatureNotSupportedException( + String.format( + "Array of type %s cannot be converted to a ResultSet", type.getCode().name())); + } + rows.add(builder.build()); + added++; + if (added == count) { + break; + } + } + } + return JdbcResultSet.of( + ResultSets.forRows( + Type.struct( + StructField.of("INDEX", Type.int64()), + StructField.of("VALUE", type.getSpannerType())), + rows.build())); } @Override public ResultSet getResultSet(long index, int count, Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException(RESULTSET_NOT_SUPPORTED); + throw new SQLFeatureNotSupportedException(RESULTSET_WITH_TYPE_MAPPING_NOT_SUPPORTED); } @Override diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java index 01184c2d..fc7bebdb 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -16,10 +16,16 @@ package com.google.cloud.spanner.jdbc; -import static org.junit.Assert.assertEquals; +import static com.google.cloud.spanner.jdbc.JdbcTypeConverter.toSqlDate; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl; import java.math.BigDecimal; import java.sql.Date; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; @@ -35,42 +41,182 @@ public void testCreateArrayTypeName() throws SQLException { // Note that JDBC array indices start at 1. JdbcArray array; array = JdbcArray.createArray("BOOL", new Boolean[] {true, false, true}); - assertEquals(array.getBaseType(), Types.BOOLEAN); - assertEquals(((Boolean[]) array.getArray(1, 1))[0], Boolean.TRUE); + assertThat(array.getBaseType()).isEqualTo(Types.BOOLEAN); + assertThat(((Boolean[]) array.getArray(1, 1))[0]).isEqualTo(Boolean.TRUE); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(true); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(false); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(2)).isEqualTo(true); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("BYTES", new byte[][] {new byte[] {1, 2}, new byte[] {3, 4}}); - assertEquals(array.getBaseType(), Types.BINARY); - assertEquals(((byte[][]) array.getArray(1, 1))[0][1], (byte) 2); + assertThat(array.getBaseType()).isEqualTo(Types.BINARY); + assertThat(((byte[][]) array.getArray(1, 1))[0][1]).isEqualTo((byte) 2); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(2)).isEqualTo(new byte[] {1, 2}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(2)).isEqualTo(new byte[] {3, 4}); + assertThat(rs.next()).isFalse(); + } array = - JdbcArray.createArray("DATE", new Date[] {new Date(1L), new Date(100L), new Date(1000L)}); - assertEquals(array.getBaseType(), Types.DATE); - assertEquals(((Date[]) array.getArray(1, 1))[0], new Date(1L)); + JdbcArray.createArray( + "DATE", + new Date[] { + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18)), + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2000, 2, 29)), + toSqlDate(com.google.cloud.Date.fromYearMonthDay(2019, 8, 31)) + }); + assertThat(array.getBaseType()).isEqualTo(Types.DATE); + assertThat(((Date[]) array.getArray(1, 1))[0]) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18))); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2021, 1, 18))); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2000, 2, 29))); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(2)) + .isEqualTo(toSqlDate(com.google.cloud.Date.fromYearMonthDay(2019, 8, 31))); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("FLOAT64", new Double[] {1.1D, 2.2D, Math.PI}); - assertEquals(array.getBaseType(), Types.DOUBLE); - assertEquals(((Double[]) array.getArray(1, 3))[2], Double.valueOf(Math.PI)); + assertThat(array.getBaseType()).isEqualTo(Types.DOUBLE); + assertThat(((Double[]) array.getArray(1, 3))[2]).isEqualTo(Double.valueOf(Math.PI)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(1.1D); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(2.2D); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(2)).isEqualTo(Math.PI); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("INT64", new Long[] {1L, 2L, 3L}); - assertEquals(array.getBaseType(), Types.BIGINT); - assertEquals(((Long[]) array.getArray(1, 1))[0], Long.valueOf(1L)); + assertThat(array.getBaseType()).isEqualTo(Types.BIGINT); + assertThat(((Long[]) array.getArray(1, 1))[0]).isEqualTo(Long.valueOf(1L)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(1L); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(2L); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(2)).isEqualTo(3L); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("NUMERIC", new BigDecimal[] {BigDecimal.ONE, null, BigDecimal.TEN}); - assertEquals(array.getBaseType(), Types.NUMERIC); - assertEquals(((BigDecimal[]) array.getArray(1, 1))[0], BigDecimal.ONE); - assertEquals(((BigDecimal[]) array.getArray(2, 1))[0], null); - assertEquals(((BigDecimal[]) array.getArray(3, 1))[0], BigDecimal.TEN); + assertThat(array.getBaseType()).isEqualTo(Types.NUMERIC); + assertThat(((BigDecimal[]) array.getArray(1, 1))[0]).isEqualTo(BigDecimal.ONE); + assertThat(((BigDecimal[]) array.getArray(2, 1))[0]).isNull(); + assertThat(((BigDecimal[]) array.getArray(3, 1))[0]).isEqualTo(BigDecimal.TEN); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isEqualTo(BigDecimal.ONE); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBigDecimal(2)).isEqualTo(BigDecimal.TEN); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); - assertEquals(array.getBaseType(), Types.NVARCHAR); - assertEquals(((String[]) array.getArray(1, 1))[0], "foo"); + assertThat(array.getBaseType()).isEqualTo(Types.NVARCHAR); + assertThat(((String[]) array.getArray(1, 1))[0]).isEqualTo("foo"); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("foo"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("bar"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("baz"); + assertThat(rs.next()).isFalse(); + } array = JdbcArray.createArray( "TIMESTAMP", new Timestamp[] {new Timestamp(1L), new Timestamp(100L), new Timestamp(1000L)}); - assertEquals(array.getBaseType(), Types.TIMESTAMP); - assertEquals(((Timestamp[]) array.getArray(1, 1))[0], new Timestamp(1L)); + assertThat(array.getBaseType()).isEqualTo(Types.TIMESTAMP); + assertThat(((Timestamp[]) array.getArray(1, 1))[0]).isEqualTo(new Timestamp(1L)); + try (ResultSet rs = array.getResultSet()) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(1L)); + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(100L)); + assertThat(rs.next()).isTrue(); + assertThat(rs.getTimestamp(2)).isEqualTo(new Timestamp(1000L)); + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void testGetResultSetMetadata() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet()) { + ResultSetMetaData metadata = rs.getMetaData(); + assertThat(metadata.getColumnCount()).isEqualTo(2); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.BIGINT); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.NVARCHAR); + assertThat(metadata.getColumnName(1)).isEqualTo("INDEX"); + assertThat(metadata.getColumnName(2)).isEqualTo("VALUE"); + } + } + + @Test + public void testGetResultSetWithIndex() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(2L, 1)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong("INDEX")).isEqualTo(2L); + assertThat(rs.getString("VALUE")).isEqualTo("bar"); + assertThat(rs.next()).isFalse(); + } + + try (ResultSet rs = array.getResultSet(1L, 5)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("foo"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("bar"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(2)).isEqualTo("baz"); + assertThat(rs.next()).isFalse(); + } + + try (ResultSet rs = array.getResultSet(1L, 0)) { + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void testGetResultSetWithInvalidIndex() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(0L, 1)) { + fail("missing expected exception"); + } catch (JdbcSqlExceptionImpl e) { + assertThat(e.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } + } + + @Test + public void testGetResultSetWithInvalidCount() throws SQLException { + JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"}); + try (ResultSet rs = array.getResultSet(1L, -1)) { + fail("missing expected exception"); + } catch (JdbcSqlExceptionImpl e) { + assertThat(e.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } } } From 8a46d000f9f1bfb8f012752092e78a15987f3617 Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Mon, 1 Feb 2021 10:21:08 +0100 Subject: [PATCH 3/3] test: add tests for invalid data types --- .../cloud/spanner/jdbc/JdbcArrayTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java index fc7bebdb..24e3e573 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -160,6 +160,32 @@ public void testCreateArrayTypeName() throws SQLException { } } + @Test + public void testCreateArrayOfArray() { + try { + JdbcArray.createArray("ARRAY", new String[][] {{}}); + fail("missing expected exception"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + JdbcSqlException jse = (JdbcSqlException) e; + assertThat(jse.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } + } + + @Test + public void testCreateArrayOfStruct() { + try { + JdbcArray.createArray("STRUCT", new Object[] {}); + fail("missing expected exception"); + } catch (SQLException e) { + assertThat((Exception) e).isInstanceOf(JdbcSqlException.class); + JdbcSqlException jse = (JdbcSqlException) e; + assertThat(jse.getErrorCode()) + .isEqualTo(ErrorCode.INVALID_ARGUMENT.getGrpcStatusCode().value()); + } + } + @Test public void testGetResultSetMetadata() throws SQLException { JdbcArray array = JdbcArray.createArray("STRING", new String[] {"foo", "bar", "baz"});