From 0b79dd92796c66e9ffecf3da06b54aa4f480b003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Szczerbi=C5=84ski?= Date: Thu, 29 Aug 2024 13:37:21 +0200 Subject: [PATCH] SNOW-1636262: `getDate` triggers NPE with 3.18.0 (regression) (#1877) --- .../client/jdbc/SnowflakeBaseResultSet.java | 32 +++++++++++++++---- .../SnowflakeResultSetSerializableV1.java | 13 ++++++-- .../snowflake/client/jdbc/ConnectionIT.java | 27 ++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 550acbe95..d9149412e 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -63,7 +63,8 @@ public abstract class SnowflakeBaseResultSet implements ResultSet { protected SnowflakeResultSetMetaDataV1 resultSetMetaData = null; protected Map parameters = new HashMap<>(); private int fetchSize = 0; - protected SFBaseSession session = null; + protected SFBaseSession session; + private final SnowflakeResultSetSerializableV1 serializable; private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); SnowflakeBaseResultSet(Statement statement) throws SQLException { @@ -71,14 +72,19 @@ public abstract class SnowflakeBaseResultSet implements ResultSet { this.resultSetType = statement.getResultSetType(); this.resultSetConcurrency = statement.getResultSetConcurrency(); this.resultSetHoldability = statement.getResultSetHoldability(); + this.session = maybeGetSession(statement); + this.serializable = null; + } + + private static SFBaseSession maybeGetSession(Statement statement) { try { - this.session = statement.unwrap(SnowflakeStatementV1.class).connection.getSFBaseSession(); + return statement.unwrap(SnowflakeStatementV1.class).connection.getSFBaseSession(); } catch (SQLException e) { // This exception shouldn't be hit. Statement class should be able to be unwrapped. logger.error( "Unable to unwrap SnowflakeStatementV1 class to retrieve session. Session is null.", false); - this.session = null; + return null; } } @@ -95,6 +101,8 @@ public SnowflakeBaseResultSet(SnowflakeResultSetSerializableV1 resultSetSerializ this.resultSetType = resultSetSerializable.getResultSetType(); this.resultSetConcurrency = resultSetSerializable.getResultSetConcurrency(); this.resultSetHoldability = resultSetSerializable.getResultSetHoldability(); + this.session = null; + this.serializable = resultSetSerializable; } /** @@ -107,6 +115,8 @@ protected SnowflakeBaseResultSet() throws SQLException { this.resultSetConcurrency = 0; this.resultSetHoldability = 0; this.statement = new SnowflakeStatementV1.NoOpSnowflakeStatementV1(); + this.session = null; + this.serializable = null; } @Override @@ -131,12 +141,22 @@ protected void raiseSQLExceptionIfResultSetIsClosed() throws SQLException { public abstract Date getDate(int columnIndex, TimeZone tz) throws SQLException; + private boolean getGetDateUseNullTimezone() { + if (this.session != null) { + return this.session.getGetDateUseNullTimezone(); + } + + if (this.serializable != null) { + return this.serializable.getGetDateUseNullTimezone(); + } + + return false; + } + @Override public Date getDate(int columnIndex) throws SQLException { raiseSQLExceptionIfResultSetIsClosed(); - return getDate( - columnIndex, - this.session.getGetDateUseNullTimezone() ? (TimeZone) null : TimeZone.getDefault()); + return getDate(columnIndex, getGetDateUseNullTimezone() ? null : TimeZone.getDefault()); } @Override diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java index 535019977..082dc2e30 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java @@ -155,6 +155,10 @@ public String toString() { int resultSetType; int resultSetConcurrency; int resultSetHoldability; + boolean treatNTZAsUTC; + boolean formatDateWithTimezone; + boolean useSessionTimezone; + boolean getDateUseNullTimezone; // Below are some metadata fields parsed from the result JSON node String queryId; @@ -173,9 +177,6 @@ public String toString() { long sendResultTime; List metaDataOfBinds = new ArrayList<>(); QueryResultFormat queryResultFormat; - boolean treatNTZAsUTC; - boolean formatDateWithTimezone; - boolean useSessionTimezone; int sessionClientMemoryLimit; // Below fields are transient, they are generated from parameters @@ -235,6 +236,7 @@ private SnowflakeResultSetSerializableV1(SnowflakeResultSetSerializableV1 toCopy this.treatNTZAsUTC = toCopy.treatNTZAsUTC; this.formatDateWithTimezone = toCopy.formatDateWithTimezone; this.useSessionTimezone = toCopy.useSessionTimezone; + this.getDateUseNullTimezone = toCopy.getDateUseNullTimezone; // Below are some metadata fields parsed from the result JSON node this.queryId = toCopy.queryId; @@ -438,6 +440,7 @@ protected SnowflakeResultSetSerializableV1( this.treatNTZAsUTC = sfSession.getTreatNTZAsUTC(); this.formatDateWithTimezone = sfSession.getFormatDateWithTimezone(); this.useSessionTimezone = sfSession.getUseSessionTimezone(); + this.getDateUseNullTimezone = sfSession.getGetDateUseNullTimezone(); // setup transient fields from parameter this.setupFieldsFromParameters(); @@ -701,6 +704,10 @@ public boolean getUseSessionTimezone() { return useSessionTimezone; } + public boolean getGetDateUseNullTimezone() { + return getDateUseNullTimezone; + } + public Optional getSession() { return possibleSession; } diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java index c63b78034..0b8115d02 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java @@ -33,8 +33,11 @@ import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; +import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.Enumeration; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; @@ -800,6 +803,30 @@ public void testStatementsAndResultSetsClosedByConnection() throws SQLException assertTrue(rs3.isClosed()); } + @Test + public void testReadDateAfterSplittingResultSet() throws Exception { + Connection conn = getConnection(); + try (Statement statement = conn.createStatement()) { + statement.execute("create or replace table table_with_date (int_c int, date_c date)"); + statement.execute("insert into table_with_date values (1, '2015-10-25')"); + + try (ResultSet rs = statement.executeQuery("select * from table_with_date")) { + final SnowflakeResultSet resultSet = rs.unwrap(SnowflakeResultSet.class); + final long arbitrarySizeInBytes = 1024; + final List serializables = + resultSet.getResultSetSerializables(arbitrarySizeInBytes); + final ArrayList dates = new ArrayList<>(); + for (SnowflakeResultSetSerializable s : serializables) { + ResultSet srs = s.getResultSet(); + srs.next(); + dates.add(srs.getDate(2)); + } + assertEquals(1, dates.size()); + assertEquals("2015-10-25", dates.get(0).toString()); + } + } + } + @Test public void testResultSetsClosedByStatement() throws SQLException { Connection connection = getConnection();