From b968eecd623fa8ec97a8286a8a405fabf3330b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 27 Apr 2021 13:48:24 +0200 Subject: [PATCH] fix: NPE was thrown when getting an array of structs from a ResultSet A NullPointerException was thrown if a ResultSet contained an array of Structs and the getArray() method was called on the column. Fixes #444 --- .../cloud/spanner/jdbc/JdbcDataType.java | 27 ++++++++++++ .../cloud/spanner/jdbc/JdbcArrayTest.java | 25 ++++++----- .../jdbc/it/ITJdbcSimpleStatementsTest.java | 42 +++++++++++++++++++ 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java index 1dc5f0e4..6e6c3bf8 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner.jdbc; import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.Code; import java.math.BigDecimal; @@ -253,6 +254,32 @@ public List getArrayElements(ResultSet rs, int columnIndex) { public Type getSpannerType() { return Type.timestamp(); } + }, + STRUCT { + @Override + public int getSqlType() { + return Types.STRUCT; + } + + @Override + public Class getJavaClass() { + return Struct.class; + } + + @Override + public Code getCode() { + return Code.STRUCT; + } + + @Override + public List getArrayElements(ResultSet rs, int columnIndex) { + return rs.getStructList(columnIndex); + } + + @Override + public Type getSpannerType() { + return Type.struct(); + } }; public abstract int getSqlType(); 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 24e3e573..f8cacd99 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java @@ -18,15 +18,19 @@ import static com.google.cloud.spanner.jdbc.JdbcTypeConverter.toSqlDate; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.Struct; 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.SQLFeatureNotSupportedException; import java.sql.Timestamp; import java.sql.Types; import org.junit.Test; @@ -174,16 +178,17 @@ public void testCreateArrayOfArray() { } @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()); - } + public void testCreateArrayOfStruct() throws SQLException { + JdbcArray array = + JdbcArray.createArray( + "STRUCT", + new Struct[] {Struct.newBuilder().set("f1").to("v1").set("f2").to(1L).build(), null}); + assertEquals(Types.STRUCT, array.getBaseType()); + assertThat((Struct[]) array.getArray()) + .asList() + .containsExactly(Struct.newBuilder().set("f1").to("v1").set("f2").to(1L).build(), null) + .inOrder(); + assertThrows(SQLFeatureNotSupportedException.class, () -> array.getResultSet()); } @Test diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcSimpleStatementsTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcSimpleStatementsTest.java index 15f25b91..fcb3b0e4 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcSimpleStatementsTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcSimpleStatementsTest.java @@ -17,14 +17,22 @@ package com.google.cloud.spanner.jdbc.it; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.cloud.spanner.IntegrationTest; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Value; import com.google.cloud.spanner.jdbc.ITAbstractJdbcTest; +import java.sql.Array; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -149,4 +157,38 @@ public void testAddBatchWhenAlreadyInBatch() { "Calling addBatch() is not allowed when a DML or DDL batch has been started on the connection."); } } + + @Test + public void testSelectArrayOfStructs() throws SQLException { + String sql = + "WITH points AS\n" + + " (SELECT [1, 5] as point\n" + + " UNION ALL SELECT [2, 8] as point\n" + + " UNION ALL SELECT [3, 7] as point\n" + + " UNION ALL SELECT [4, 1] as point\n" + + " UNION ALL SELECT [5, 7] as point)\n" + + "SELECT ARRAY(\n" + + " SELECT STRUCT(point)\n" + + " FROM points)\n" + + " AS coordinates"; + try (Connection connection = createConnection()) { + try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { + assertTrue(resultSet.next()); + assertEquals(resultSet.getMetaData().getColumnCount(), 1); + Array array = resultSet.getArray(1); + assertThat((Struct[]) array.getArray()) + .asList() + .containsExactly( + Struct.newBuilder().set("point").to(Value.int64Array(new long[] {1L, 5L})).build(), + Struct.newBuilder().set("point").to(Value.int64Array(new long[] {2L, 8L})).build(), + Struct.newBuilder().set("point").to(Value.int64Array(new long[] {3L, 7L})).build(), + Struct.newBuilder().set("point").to(Value.int64Array(new long[] {4L, 1L})).build(), + Struct.newBuilder().set("point").to(Value.int64Array(new long[] {5L, 7L})).build()); + // Getting a result set from an array of structs is not supported, as structs are not + // supported as a valid column type in a result set. + assertThrows(SQLFeatureNotSupportedException.class, () -> array.getResultSet()); + assertFalse(resultSet.next()); + } + } + } }