From 8891c799baa0a1c45640dbbc1e11f55bb60dae22 Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Thu, 7 Mar 2024 02:58:19 -0800 Subject: [PATCH 1/9] Added retry logic for authentication with Okta --- .../snowflake/client/core/SessionUtil.java | 37 ++++++--- .../client/core/SessionUtilLatestIT.java | 75 +++++++++++++++++++ 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index 9a51cd8ae..32cb2d39d 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -651,16 +651,30 @@ private static SFLoginOutput newSession( loginInput.getHttpClientSettingsKey()); } catch (SnowflakeSQLException ex) { if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) { - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { - SessionUtilKeyPair s = - new SessionUtilKeyPair( - loginInput.getPrivateKey(), - loginInput.getPrivateKeyFile(), - loginInput.getPrivateKeyFilePwd(), - loginInput.getAccountName(), - loginInput.getUserName()); - - data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); + if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT + || authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + + if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { + SessionUtilKeyPair s = + new SessionUtilKeyPair( + loginInput.getPrivateKey(), + loginInput.getPrivateKeyFile(), + loginInput.getPrivateKeyFilePwd(), + loginInput.getAccountName(), + loginInput.getUserName()); + + data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); + } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + // If we need to retry, we need to get a new Okta token + tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); + data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse); + authnData.setData(data); + String updatedJson = mapper.writeValueAsString(authnData); + + StringEntity updatedInput = new StringEntity(updatedJson, StandardCharsets.UTF_8); + updatedInput.setContentType("application/json"); + postRequest.setEntity(updatedInput); + } long elapsedSeconds = ex.getElapsedSeconds(); @@ -690,7 +704,8 @@ private static SFLoginOutput newSession( } } - // JWT renew should not count as a retry, so we pass back the current retry count. + // JWT or Okta renew should not count as a retry, so we pass back the current retry + // count. retryCount = ex.getRetryCount(); continue; diff --git a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java index f3e60f56b..bedd77438 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java @@ -390,4 +390,79 @@ public void testOktaAuthGetFail() throws Throwable { assertEquals(SqlState.IO_ERROR, e.getSQLState()); } } + + private SFLoginInput createOktaLoginInput() { + SFLoginInput input = new SFLoginInput(); + input.setServerUrl("https://testauth.okta.com"); + input.setUserName("MOCK_USERNAME"); + input.setPassword("MOCK_PASSWORD"); + input.setAccountName("MOCK_ACCOUNT_NAME"); + input.setAppId("MOCK_APP_ID"); + input.setOCSPMode(OCSPMode.FAIL_OPEN); + input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN)); + input.setLoginTimeout(1000); + input.setSessionParameters(new HashMap<>()); + input.setAuthenticator("https://testauth.okta.com"); + return input; + } + + // Testing retry with Okta calls the service to get a new unique token. This is valid after + // version 3.15.0. + @Test + public void testOktaAuthRetry() throws Throwable { + SFLoginInput loginInput = createOktaLoginInput(); + Map connectionPropertiesMap = initConnectionPropertiesMap(); + SnowflakeSQLException ex = + new SnowflakeSQLException(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT, 0, true, 0); + try (MockedStatic mockedHttpUtil = mockStatic(HttpUtil.class)) { + mockedHttpUtil + .when( + () -> + HttpUtil.executeGeneralRequest( + Mockito.any(HttpPost.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn( + "{\"data\":{\"tokenUrl\":\"https://testauth.okta.com/api/v1/authn\"," + + "\"ssoUrl\":\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\"," + + "\"proofKey\":null},\"code\":null,\"message\":null,\"success\":true}") + .thenThrow(ex) + .thenReturn( + "{\"data\":{\"tokenUrl\":\"https://testauth.okta.com/api/v1/authn\"," + + "\"ssoUrl\":\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\"," + + "\"proofKey\":null},\"code\":null,\"message\":null,\"success\":true}"); + + mockedHttpUtil + .when( + () -> + HttpUtil.executeRequestWithoutCookies( + Mockito.any(HttpRequestBase.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(AtomicBoolean.class), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn( + "{\"expiresAt\":\"2023-10-13T19:18:09.000Z\",\"status\":\"SUCCESS\",\"sessionToken\":\"testsessiontoken\"}"); + + mockedHttpUtil + .when( + () -> + HttpUtil.executeGeneralRequest( + Mockito.any(HttpGet.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn("
"); + + SessionUtil.openSession(loginInput, connectionPropertiesMap, "ALL"); + } + } } From 32b42b34905b1b2adf491a78c93bd137d6f6e8f9 Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Thu, 7 Mar 2024 02:58:19 -0800 Subject: [PATCH 2/9] Added retry logic for authentication with Okta --- .../snowflake/client/core/SessionUtil.java | 37 ++++++--- .../client/core/SessionUtilLatestIT.java | 75 +++++++++++++++++++ 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index 9a51cd8ae..32cb2d39d 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -651,16 +651,30 @@ private static SFLoginOutput newSession( loginInput.getHttpClientSettingsKey()); } catch (SnowflakeSQLException ex) { if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) { - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { - SessionUtilKeyPair s = - new SessionUtilKeyPair( - loginInput.getPrivateKey(), - loginInput.getPrivateKeyFile(), - loginInput.getPrivateKeyFilePwd(), - loginInput.getAccountName(), - loginInput.getUserName()); - - data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); + if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT + || authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + + if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { + SessionUtilKeyPair s = + new SessionUtilKeyPair( + loginInput.getPrivateKey(), + loginInput.getPrivateKeyFile(), + loginInput.getPrivateKeyFilePwd(), + loginInput.getAccountName(), + loginInput.getUserName()); + + data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); + } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + // If we need to retry, we need to get a new Okta token + tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); + data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse); + authnData.setData(data); + String updatedJson = mapper.writeValueAsString(authnData); + + StringEntity updatedInput = new StringEntity(updatedJson, StandardCharsets.UTF_8); + updatedInput.setContentType("application/json"); + postRequest.setEntity(updatedInput); + } long elapsedSeconds = ex.getElapsedSeconds(); @@ -690,7 +704,8 @@ private static SFLoginOutput newSession( } } - // JWT renew should not count as a retry, so we pass back the current retry count. + // JWT or Okta renew should not count as a retry, so we pass back the current retry + // count. retryCount = ex.getRetryCount(); continue; diff --git a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java index f3e60f56b..bedd77438 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java @@ -390,4 +390,79 @@ public void testOktaAuthGetFail() throws Throwable { assertEquals(SqlState.IO_ERROR, e.getSQLState()); } } + + private SFLoginInput createOktaLoginInput() { + SFLoginInput input = new SFLoginInput(); + input.setServerUrl("https://testauth.okta.com"); + input.setUserName("MOCK_USERNAME"); + input.setPassword("MOCK_PASSWORD"); + input.setAccountName("MOCK_ACCOUNT_NAME"); + input.setAppId("MOCK_APP_ID"); + input.setOCSPMode(OCSPMode.FAIL_OPEN); + input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN)); + input.setLoginTimeout(1000); + input.setSessionParameters(new HashMap<>()); + input.setAuthenticator("https://testauth.okta.com"); + return input; + } + + // Testing retry with Okta calls the service to get a new unique token. This is valid after + // version 3.15.0. + @Test + public void testOktaAuthRetry() throws Throwable { + SFLoginInput loginInput = createOktaLoginInput(); + Map connectionPropertiesMap = initConnectionPropertiesMap(); + SnowflakeSQLException ex = + new SnowflakeSQLException(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT, 0, true, 0); + try (MockedStatic mockedHttpUtil = mockStatic(HttpUtil.class)) { + mockedHttpUtil + .when( + () -> + HttpUtil.executeGeneralRequest( + Mockito.any(HttpPost.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn( + "{\"data\":{\"tokenUrl\":\"https://testauth.okta.com/api/v1/authn\"," + + "\"ssoUrl\":\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\"," + + "\"proofKey\":null},\"code\":null,\"message\":null,\"success\":true}") + .thenThrow(ex) + .thenReturn( + "{\"data\":{\"tokenUrl\":\"https://testauth.okta.com/api/v1/authn\"," + + "\"ssoUrl\":\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\"," + + "\"proofKey\":null},\"code\":null,\"message\":null,\"success\":true}"); + + mockedHttpUtil + .when( + () -> + HttpUtil.executeRequestWithoutCookies( + Mockito.any(HttpRequestBase.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(AtomicBoolean.class), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn( + "{\"expiresAt\":\"2023-10-13T19:18:09.000Z\",\"status\":\"SUCCESS\",\"sessionToken\":\"testsessiontoken\"}"); + + mockedHttpUtil + .when( + () -> + HttpUtil.executeGeneralRequest( + Mockito.any(HttpGet.class), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.anyInt(), + Mockito.nullable(HttpClientSettingsKey.class))) + .thenReturn("
"); + + SessionUtil.openSession(loginInput, connectionPropertiesMap, "ALL"); + } + } } From 653635c6769f8cf63b9f0b245b01b97914d70ea4 Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:23:33 -0700 Subject: [PATCH 3/9] Add log message and testing --- .../net/snowflake/client/core/SessionUtil.java | 1 + .../net/snowflake/client/jdbc/RestRequestTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index 32cb2d39d..4003cd823 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -665,6 +665,7 @@ private static SFLoginOutput newSession( data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + logger.debug("Retrieve new token for Okta authentication."); // If we need to retry, we need to get a new Okta token tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse); diff --git a/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java b/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java index 0654cc73d..e2988e06e 100644 --- a/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java +++ b/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java @@ -359,6 +359,20 @@ public void testExceptionAuthBasedTimeout() throws IOException { } } + @Test + public void testExceptionAuthBasedTimeoutFor429ErrorCode() throws IOException { + CloseableHttpClient client = mock(CloseableHttpClient.class); + when(client.execute(any(HttpUriRequest.class))) + .thenAnswer((Answer) invocation -> retryLoginResponse()); + + try { + execute(client, "login-request.com/?requestId=abcd-1234", 2, 1, 30000, true, false); + } catch (SnowflakeSQLException ex) { + assertThat( + ex.getErrorCode(), equalTo(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode())); + } + } + @Test public void testNoRetry() throws IOException, SnowflakeSQLException { boolean telemetryEnabled = TelemetryService.getInstance().isEnabled(); From 8f9be45d4bc44780d979a6fe28ada855f754e441 Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:28:15 -0700 Subject: [PATCH 4/9] Revert "SNOW-974575 Structured type array map (#1654)" This reverts commit 75c57f29ea245a2555a54525ac3d051fe70bbef1. --- .../snowflake/client/core/JsonSqlInput.java | 3 +- .../client/core/SFArrowResultSet.java | 7 - .../client/core/SFBaseResultSet.java | 10 - .../client/core/SFJsonResultSet.java | 221 ++------------ .../snowflake/client/core/SFResultSet.java | 17 +- .../client/core/SFResultSetMetaData.java | 3 +- .../net/snowflake/client/core/SfSqlArray.java | 77 ----- .../client/core/json/Converters.java | 155 ---------- .../client/core/json/NumberConverter.java | 2 +- .../core/structs/SQLDataCreationHelper.java | 3 +- .../client/jdbc/SFAsyncResultSet.java | 6 +- .../client/jdbc/SnowflakeBaseResultSet.java | 71 +---- .../jdbc/SnowflakeResultSetMetaDataV1.java | 3 +- .../client/jdbc/SnowflakeResultSetV1.java | 13 +- .../snowflake/client/jdbc/SnowflakeType.java | 1 - .../snowflake/client/jdbc/SnowflakeUtil.java | 51 +--- .../util/JsonStringToTypeConverter.java | 14 - .../java/net/snowflake/client/TestUtil.java | 6 +- .../snowflake/client/ThrowingConsumer.java | 4 +- .../client/jdbc/MockConnectionTest.java | 16 - .../jdbc/ResultSetFeatureNotSupportedIT.java | 1 + .../ResultSetStructuredTypesLatestIT.java | 281 ++---------------- 22 files changed, 99 insertions(+), 866 deletions(-) delete mode 100644 src/main/java/net/snowflake/client/core/SfSqlArray.java delete mode 100644 src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java diff --git a/src/main/java/net/snowflake/client/core/JsonSqlInput.java b/src/main/java/net/snowflake/client/core/JsonSqlInput.java index f82e492a1..3362f3a25 100644 --- a/src/main/java/net/snowflake/client/core/JsonSqlInput.java +++ b/src/main/java/net/snowflake/client/core/JsonSqlInput.java @@ -195,8 +195,7 @@ public Timestamp readTimestamp(TimeZone tz) throws SQLException { int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); int columnSubType = fieldMetadata.getType(); int scale = fieldMetadata.getScale(); - Timestamp result = - SnowflakeUtil.getTimestampFromType(columnSubType, (String) value, session); + Timestamp result = getTimestampFromType(columnSubType, (String) value); if (result != null) { return result; } diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index 94b640b9a..e7c90d3f9 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; -import java.sql.Array; import java.sql.Date; import java.sql.SQLException; import java.sql.Time; @@ -509,12 +508,6 @@ public Object getObject(int columnIndex) throws SFException { return handleObjectType(columnIndex, obj); } - @Override - public Array getArray(int columnIndex) throws SFException { - // TODO: handleArray SNOW-969794 - throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type ARRAY"); - } - private Object handleObjectType(int columnIndex, Object obj) throws SFException { int columnType = resultSetMetaData.getColumnType(columnIndex); if (columnType == Types.STRUCT diff --git a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java index 4bbc9bf6c..f34bb2d53 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java @@ -5,7 +5,6 @@ package net.snowflake.client.core; import java.math.BigDecimal; -import java.sql.Array; import java.sql.Date; import java.sql.SQLException; import java.sql.Time; @@ -15,7 +14,6 @@ import java.util.List; import java.util.Map; import java.util.TimeZone; -import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.SnowflakeResultSetSerializable; import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; @@ -95,8 +93,6 @@ public abstract class SFBaseResultSet { public abstract Object getObject(int columnIndex) throws SFException; - public abstract Array getArray(int columnIndex) throws SFException; - public abstract BigDecimal getBigDecimal(int columnIndex) throws SFException; public abstract BigDecimal getBigDecimal(int columnIndex, int scale) throws SFException; @@ -197,10 +193,4 @@ public List getResultSetSerializables(long maxSi throws SQLException { return this.resultSetSerializable.splitBySize(maxSizeInBytes); } - - @SnowflakeJdbcInternalApi - public Converters getConverters() { - logger.debug("Json converters weren't created"); - return null; - } } diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index 21cec8a0f..026d1d240 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -7,28 +7,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; import java.math.BigDecimal; -import java.sql.Array; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.util.Iterator; -import java.util.Map; -import java.util.Spliterator; -import java.util.Spliterators; import java.util.TimeZone; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.ErrorCode; -import net.snowflake.client.jdbc.FieldMetadata; -import net.snowflake.client.jdbc.SnowflakeColumnMetadata; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.client.util.JsonStringToTypeConverter; /** Abstract class used to represent snowflake result set in json format */ public abstract class SFJsonResultSet extends SFBaseResultSet { @@ -95,27 +83,40 @@ public Object getObject(int columnIndex) throws SFException { return getBoolean(columnIndex); case Types.STRUCT: - if (Boolean.parseBoolean(System.getProperty(STRUCTURED_TYPE_ENABLED_PROPERTY_NAME))) { + if (Boolean.valueOf(System.getProperty(STRUCTURED_TYPE_ENABLED_PROPERTY_NAME))) { return getSqlInput((String) obj, columnIndex); } else { throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } - case Types.ARRAY: - if (Boolean.parseBoolean(System.getProperty(STRUCTURED_TYPE_ENABLED_PROPERTY_NAME))) { - return getArray(columnIndex); - } else { - throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); - } default: throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } } - @Override - public Array getArray(int columnIndex) throws SFException { - Object obj = getObjectInternal(columnIndex); - return getArrayInternal((String) obj, columnIndex); + private Object getSqlInput(String input, int columnIndex) throws SFException { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree(input); + return new JsonSqlInput( + jsonNode, + session, + converters, + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); + } catch (JsonProcessingException e) { + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); + } + } + + /** + * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type + * instead. + * + * @param columnIndex the column index + * @return an object of type long or BigDecimal depending on number size + * @throws SFException + */ + private Object getBigInt(int columnIndex, Object obj) throws SFException { + return converters.getNumberConverter().getBigInt(obj, columnIndex); } @Override @@ -249,178 +250,4 @@ public Date getDate(int columnIndex, TimeZone tz) throws SFException { private Timestamp getTimestamp(int columnIndex) throws SFException { return getTimestamp(columnIndex, TimeZone.getDefault()); } - - @Override - @SnowflakeJdbcInternalApi - public Converters getConverters() { - return converters; - } - - private Object getSqlInput(String input, int columnIndex) throws SFException { - try { - JsonNode jsonNode = OBJECT_MAPPER.readTree(input); - return new JsonSqlInput( - jsonNode, - session, - converters, - resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - } - - private SfSqlArray getArrayInternal(String obj, int columnIndex) throws SFException { - try { - SnowflakeColumnMetadata arrayMetadata = - resultSetMetaData.getColumnMetadata().get(columnIndex - 1); - FieldMetadata fieldMetadata = arrayMetadata.getFields().get(0); - - int columnSubType = fieldMetadata.getType(); - int columnType = ColumnTypeHelper.getColumnType(columnSubType, session); - int scale = fieldMetadata.getScale(); - - ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(obj); - Iterator nodeElements = arrayNode.elements(); - - switch (columnSubType) { - case Types.INTEGER: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.integerConverter(columnType)) - .toArray(Integer[]::new)); - case Types.SMALLINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.smallIntConverter(columnType)) - .toArray(Short[]::new)); - case Types.TINYINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.tinyIntConverter(columnType)) - .toArray(Byte[]::new)); - case Types.BIGINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.bigIntConverter(columnType)).toArray(Long[]::new)); - case Types.DECIMAL: - case Types.NUMERIC: - return new SfSqlArray( - columnSubType, - convertToFixedArray(nodeElements, converters.bigDecimalConverter(columnType))); - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGNVARCHAR: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.varcharConverter(columnType, columnSubType, scale)) - .toArray(String[]::new)); - case Types.BINARY: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.bytesConverter(columnType, scale)) - .toArray(Byte[][]::new)); - case Types.FLOAT: - case Types.REAL: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.floatConverter(columnType)).toArray(Float[]::new)); - case Types.DOUBLE: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.doubleConverter(columnType)) - .toArray(Double[]::new)); - case Types.DATE: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.dateConverter(session)).toArray(Date[]::new)); - case Types.TIME: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.timeConverter(session)).toArray(Time[]::new)); - case Types.TIMESTAMP: - return new SfSqlArray( - columnSubType, - getStream( - nodeElements, - converters.timestampConverter(columnSubType, columnType, scale, session)) - .toArray(Timestamp[]::new)); - case Types.BOOLEAN: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.booleanConverter(columnType)) - .toArray(Boolean[]::new)); - case Types.STRUCT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.structConverter(OBJECT_MAPPER)) - .toArray(Map[]::new)); - case Types.ARRAY: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.arrayConverter(OBJECT_MAPPER)) - .toArray(Map[][]::new)); - default: - throw new SFException( - ErrorCode.FEATURE_UNSUPPORTED, - "Can't construct array for data type: " + columnSubType); - } - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - } - - private Object[] convertToFixedArray( - Iterator nodeElements, JsonStringToTypeConverter bigIntConverter) { - AtomicInteger bigDecimalCount = new AtomicInteger(); - Object[] elements = - getStream(nodeElements, bigIntConverter) - .peek( - elem -> { - if (elem instanceof BigDecimal) { - bigDecimalCount.incrementAndGet(); - } - }) - .toArray( - size -> { - boolean shouldbbeReturnAsBigDecimal = bigDecimalCount.get() > 0; - Class returnedClass = - shouldbbeReturnAsBigDecimal ? BigDecimal.class : Long.class; - return java.lang.reflect.Array.newInstance(returnedClass, size); - }); - return elements; - } - - private Stream getStream(Iterator nodeElements, JsonStringToTypeConverter converter) { - return StreamSupport.stream( - Spliterators.spliteratorUnknownSize(nodeElements, Spliterator.ORDERED), false) - .map( - elem -> { - try { - return convert(converter, (JsonNode) elem); - } catch (SFException e) { - throw new RuntimeException(e); - } - }); - } - - private static Object convert(JsonStringToTypeConverter converter, JsonNode node) - throws SFException { - if (node.isValueNode()) { - return converter.convert(node.asText()); - } else { - return converter.convert(node.toString()); - } - } - - /** - * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type - * instead. - * - * @param columnIndex the column index - * @return an object of type long or BigDecimal depending on number size - * @throws SFException - */ - private Object getBigInt(int columnIndex, Object obj) throws SFException { - return converters.getNumberConverter().getBigInt(obj, columnIndex); - } } diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index b7698cf5d..77f30ddd7 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -155,7 +155,22 @@ public SFResultSet( Telemetry telemetryClient, boolean sortResult) throws SQLException { - super(resultSetSerializable.getTimeZone(), new Converters(session, resultSetSerializable)); + super( + resultSetSerializable.getTimeZone(), + new Converters( + resultSetSerializable.getTimeZone(), + session, + resultSetSerializable.getResultVersion(), + resultSetSerializable.isHonorClientTZForTimestampNTZ(), + resultSetSerializable.getTreatNTZAsUTC(), + resultSetSerializable.getUseSessionTimezone(), + resultSetSerializable.getFormatDateWithTimeZone(), + resultSetSerializable.getBinaryFormatter(), + resultSetSerializable.getDateFormatter(), + resultSetSerializable.getTimeFormatter(), + resultSetSerializable.getTimestampNTZFormatter(), + resultSetSerializable.getTimestampLTZFormatter(), + resultSetSerializable.getTimestampTZFormatter())); this.resultSetSerializable = resultSetSerializable; this.columnCount = 0; this.sortResult = sortResult; diff --git a/src/main/java/net/snowflake/client/core/SFResultSetMetaData.java b/src/main/java/net/snowflake/client/core/SFResultSetMetaData.java index ce129788b..dfb621400 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSetMetaData.java +++ b/src/main/java/net/snowflake/client/core/SFResultSetMetaData.java @@ -473,8 +473,7 @@ public List getIsAutoIncrementList() { return isAutoIncrementList; } - @SnowflakeJdbcInternalApi - public List getColumnMetadata() { + List getColumnMetadata() { return columnMetadata; } } diff --git a/src/main/java/net/snowflake/client/core/SfSqlArray.java b/src/main/java/net/snowflake/client/core/SfSqlArray.java deleted file mode 100644 index 83270796a..000000000 --- a/src/main/java/net/snowflake/client/core/SfSqlArray.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.snowflake.client.core; - -import java.sql.Array; -import java.sql.JDBCType; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.Map; - -@SnowflakeJdbcInternalApi -public class SfSqlArray implements Array { - - private int baseType; - private Object elements; - - public SfSqlArray(int baseType, Object elements) { - this.baseType = baseType; - this.elements = elements; - } - - @Override - public String getBaseTypeName() throws SQLException { - return JDBCType.valueOf(baseType).getName(); - } - - @Override - public int getBaseType() throws SQLException { - return baseType; - } - - @Override - public Object getArray() throws SQLException { - return elements; - } - - @Override - public Object getArray(Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException("getArray(Map> map)"); - } - - @Override - public Object getArray(long index, int count) throws SQLException { - throw new SQLFeatureNotSupportedException("getArray(long index, int count)"); - } - - @Override - public Object getArray(long index, int count, Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException( - "getArray(long index, int count, Map> map)"); - } - - @Override - public ResultSet getResultSet() throws SQLException { - throw new SQLFeatureNotSupportedException( - "getArray(long index, int count, Map> map)"); - } - - @Override - public ResultSet getResultSet(Map> map) throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSet(Map> map)"); - } - - @Override - public ResultSet getResultSet(long index, int count) throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSet(long index, int count)"); - } - - @Override - public ResultSet getResultSet(long index, int count, Map> map) - throws SQLException { - throw new SQLFeatureNotSupportedException( - "getResultSet(long index, int count, Map> map)"); - } - - @Override - public void free() throws SQLException {} -} diff --git a/src/main/java/net/snowflake/client/core/json/Converters.java b/src/main/java/net/snowflake/client/core/json/Converters.java index c94361f51..fa3baadb6 100644 --- a/src/main/java/net/snowflake/client/core/json/Converters.java +++ b/src/main/java/net/snowflake/client/core/json/Converters.java @@ -1,26 +1,8 @@ package net.snowflake.client.core.json; -import static net.snowflake.client.jdbc.SnowflakeUtil.getTimestampFromType; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.time.ZoneOffset; -import java.util.Arrays; -import java.util.Map; import java.util.TimeZone; import net.snowflake.client.core.SFBaseSession; -import net.snowflake.client.core.SFException; -import net.snowflake.client.core.SnowflakeJdbcInternalApi; -import net.snowflake.client.jdbc.ErrorCode; -import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; -import net.snowflake.client.util.JsonStringToTypeConverter; import net.snowflake.common.core.SFBinaryFormat; -import net.snowflake.common.core.SFTimestamp; import net.snowflake.common.core.SnowflakeDateTimeFormat; public class Converters { @@ -70,24 +52,6 @@ public Converters( this); } - @SnowflakeJdbcInternalApi - public Converters(SFBaseSession session, SnowflakeResultSetSerializableV1 resultSetSerializable) { - this( - resultSetSerializable.getTimeZone(), - session, - resultSetSerializable.getResultVersion(), - resultSetSerializable.isHonorClientTZForTimestampNTZ(), - resultSetSerializable.getTreatNTZAsUTC(), - resultSetSerializable.getUseSessionTimezone(), - resultSetSerializable.getFormatDateWithTimeZone(), - resultSetSerializable.getBinaryFormatter(), - resultSetSerializable.getDateFormatter(), - resultSetSerializable.getTimeFormatter(), - resultSetSerializable.getTimestampNTZFormatter(), - resultSetSerializable.getTimestampLTZFormatter(), - resultSetSerializable.getTimestampTZFormatter()); - } - public BooleanConverter getBooleanConverter() { return booleanConverter; } @@ -107,123 +71,4 @@ public BytesConverter getBytesConverter() { public StringConverter getStringConverter() { return stringConverter; } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter integerConverter(int columnType) { - return value -> getNumberConverter().getInt(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter smallIntConverter(int columnType) { - return value -> getNumberConverter().getShort(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter tinyIntConverter(int columnType) { - return value -> getNumberConverter().getByte(value); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bigIntConverter(int columnType) { - return value -> getNumberConverter().getBigInt(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter longConverter(int columnType) { - return value -> getNumberConverter().getLong(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bigDecimalConverter(int columnType) { - return value -> getNumberConverter().getBigDecimal(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter floatConverter(int columnType) { - return value -> getNumberConverter().getBigDecimal(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter doubleConverter(int columnType) { - return value -> getNumberConverter().getBigDecimal(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bytesConverter(int columnType, int scale) { - return value -> { - byte[] primitiveArray = getBytesConverter().getBytes(value, columnType, Types.BINARY, scale); - Byte[] newByteArray = new Byte[primitiveArray.length]; - Arrays.setAll(newByteArray, n -> primitiveArray[n]); - return newByteArray; - }; - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter varcharConverter(int columnType, int columnSubType, int scale) { - return value -> getStringConverter().getString(value, columnType, columnSubType, scale); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter booleanConverter(int columnType) { - return value -> getBooleanConverter().getBoolean(value, columnType); - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter dateConverter(SFBaseSession session) { - return value -> { - SnowflakeDateTimeFormat formatter = - SnowflakeDateTimeFormat.fromSqlFormat( - (String) session.getCommonParameters().get("DATE_OUTPUT_FORMAT")); - SFTimestamp timestamp = formatter.parse(value); - return Date.valueOf( - Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalDate()); - }; - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter timeConverter(SFBaseSession session) { - return value -> { - SnowflakeDateTimeFormat formatter = - SnowflakeDateTimeFormat.fromSqlFormat( - (String) session.getCommonParameters().get("TIME_OUTPUT_FORMAT")); - SFTimestamp timestamp = formatter.parse((String) value); - return Time.valueOf( - Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalTime()); - }; - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter timestampConverter( - int columnSubType, int columnType, int scale, SFBaseSession session) { - return value -> { - Timestamp result = getTimestampFromType(columnSubType, (String) value, session); - if (result != null) { - return result; - } - return getDateTimeConverter() - .getTimestamp(value, columnType, columnSubType, TimeZone.getDefault(), scale); - }; - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter structConverter(ObjectMapper objectMapper) { - return value -> { - try { - return objectMapper.readValue(value, Map.class); - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - }; - } - - @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter arrayConverter(ObjectMapper objectMapper) { - return value -> { - try { - return objectMapper.readValue(value, Map[].class); - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - }; - } } diff --git a/src/main/java/net/snowflake/client/core/json/NumberConverter.java b/src/main/java/net/snowflake/client/core/json/NumberConverter.java index 18f6e96b2..132003359 100644 --- a/src/main/java/net/snowflake/client/core/json/NumberConverter.java +++ b/src/main/java/net/snowflake/client/core/json/NumberConverter.java @@ -115,7 +115,7 @@ public BigDecimal getBigDecimal(Object obj, int columnType, Integer scale) throw if (obj == null) { return null; } - BigDecimal value = getBigDecimal(obj.toString(), columnType); + BigDecimal value = new BigDecimal(obj.toString()); value = value.setScale(scale, RoundingMode.HALF_UP); return value; } diff --git a/src/main/java/net/snowflake/client/core/structs/SQLDataCreationHelper.java b/src/main/java/net/snowflake/client/core/structs/SQLDataCreationHelper.java index fd3257e79..5cfdb5ca2 100644 --- a/src/main/java/net/snowflake/client/core/structs/SQLDataCreationHelper.java +++ b/src/main/java/net/snowflake/client/core/structs/SQLDataCreationHelper.java @@ -13,10 +13,11 @@ public class SQLDataCreationHelper { public static T create(Class type) throws SQLException { Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); - return (T) + SQLData instance = typeFactory .map(Supplier::get) .orElseGet(() -> createUsingReflection((Class) type)); + return (T) instance; } private static SQLData createUsingReflection(Class type) { diff --git a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java index c50bf4900..bc164de89 100644 --- a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java @@ -26,6 +26,7 @@ /** SFAsyncResultSet implementation. Note: For Snowflake internal use */ public class SFAsyncResultSet extends SnowflakeBaseResultSet implements SnowflakeResultSet, ResultSet { + private final SFBaseResultSet sfBaseResultSet; private ResultSet resultSetForNext = new SnowflakeResultSetV1.EmptyResultSet(); private boolean resultSetForNextInitialized = false; private String queryID; @@ -46,8 +47,8 @@ public class SFAsyncResultSet extends SnowflakeBaseResultSet * @throws SQLException if failed to construct snowflake result set metadata */ SFAsyncResultSet(SFBaseResultSet sfBaseResultSet, Statement statement) throws SQLException { - super(statement); + this.sfBaseResultSet = sfBaseResultSet; this.queryID = sfBaseResultSet.getQueryId(); this.session = sfBaseResultSet.getSession(); this.extraStatement = statement; @@ -69,6 +70,8 @@ public SFAsyncResultSet( throws SQLException { super(resultSetSerializable); this.queryID = sfBaseResultSet.getQueryId(); + this.sfBaseResultSet = sfBaseResultSet; + this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData()); this.resultSetMetaData.setQueryIdForAsyncResults(this.queryID); this.resultSetMetaData.setQueryType(SnowflakeResultSetMetaDataV1.QueryType.ASYNC); @@ -76,6 +79,7 @@ public SFAsyncResultSet( public SFAsyncResultSet(String queryID, Statement statement) throws SQLException { super(statement); + this.sfBaseResultSet = null; queryID.trim(); if (!QueryIdValidator.isValid(queryID)) { throw new SQLException( diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 7958a5b77..77efb4362 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -4,9 +4,6 @@ package net.snowflake.client.jdbc; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; @@ -29,17 +26,14 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; -import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TimeZone; -import net.snowflake.client.core.JsonSqlInput; -import net.snowflake.client.core.ObjectMapperFactory; -import net.snowflake.client.core.SFBaseResultSet; +import java.util.function.Supplier; import net.snowflake.client.core.SFBaseSession; -import net.snowflake.client.core.structs.SQLDataCreationHelper; +import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; @@ -50,7 +44,6 @@ public abstract class SnowflakeBaseResultSet implements ResultSet { private final int resultSetType; private final int resultSetConcurrency; private final int resultSetHoldability; - protected SFBaseResultSet sfBaseResultSet; // Snowflake supports sessionless result set. For this case, there is no // statement for this result set. protected final Statement statement; @@ -58,7 +51,6 @@ public abstract class SnowflakeBaseResultSet implements ResultSet { protected Map parameters = new HashMap<>(); private int fetchSize = 0; protected SFBaseSession session = null; - private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); SnowflakeBaseResultSet(Statement statement) throws SQLException { this.statement = statement; @@ -1347,62 +1339,25 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { public T getObject(int columnIndex, Class type) throws SQLException { logger.debug("public T getObject(int columnIndex,Class type)", false); if (SQLData.class.isAssignableFrom(type)) { - SQLData instance = (SQLData) SQLDataCreationHelper.create(type); + Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); + SQLData instance = + typeFactory + .map(Supplier::get) + .orElseGet(() -> createUsingReflection((Class) type)); SQLInput sqlInput = (SQLInput) getObject(columnIndex); instance.readSQL(sqlInput, null); return (T) instance; - } else if (Map.class.isAssignableFrom(type)) { - JsonNode jsonNode = ((JsonSqlInput) getObject(columnIndex)).getInput(); - return (T) OBJECT_MAPPER.convertValue(jsonNode, new TypeReference>() {}); } else { return (T) getObject(columnIndex); } } - public List getList(int columnIndex, Class type) throws SQLException { - T[] sqlInputs = getArray(columnIndex, type); - return Arrays.asList(sqlInputs); - } - - public T[] getArray(int columnIndex, Class type) throws SQLException { - Map[] jsonMaps = (Map[]) getArray(columnIndex).getArray(); - T[] arr = (T[]) java.lang.reflect.Array.newInstance(type, jsonMaps.length); - int counter = 0; - for (Map map : jsonMaps) { - SQLData instance = (SQLData) SQLDataCreationHelper.create(type); - SQLInput sqlInput = - new JsonSqlInput( - OBJECT_MAPPER.convertValue(map, JsonNode.class), - session, - sfBaseResultSet.getConverters(), - sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields()); - instance.readSQL(sqlInput, null); - arr[counter++] = (T) instance; - } - - return arr; - } - - public Map getMap(int columnIndex, Class type) throws SQLException { - Object object = getObject(columnIndex); - JsonNode jsonNode = ((JsonSqlInput) object).getInput(); - Map map = - OBJECT_MAPPER.convertValue(jsonNode, new TypeReference>() {}); - Map resultMap = new HashMap<>(); - - for (Map.Entry entry : map.entrySet()) { - SQLData instance = (SQLData) SQLDataCreationHelper.create(type); - SQLInput sqlInput = - new JsonSqlInput( - jsonNode.get(entry.getKey()), - session, - sfBaseResultSet.getConverters(), - sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields()); - instance.readSQL(sqlInput, null); - resultMap.put(entry.getKey(), (T) instance); + private SQLData createUsingReflection(Class type) { + try { + return type.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); } - - return resultMap; } @Override diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java index 4bc90bd54..94f4bab11 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java @@ -106,11 +106,10 @@ public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); switch (colType) { - // Note: SF types GEOGRAPHY, GEOMETRY are also represented as VARCHAR. + // Note: SF types ARRAY, GEOGRAPHY, GEOMETRY are also represented as VARCHAR. case Types.VARCHAR: case Types.CHAR: case Types.STRUCT: - case Types.ARRAY: return true; case Types.INTEGER: diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java index 02909f4ff..30938b0e4 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java @@ -35,6 +35,7 @@ /** Snowflake ResultSet implementation */ public class SnowflakeResultSetV1 extends SnowflakeBaseResultSet implements SnowflakeResultSet, ResultSet { + private final SFBaseResultSet sfBaseResultSet; /** * Constructor takes an inputstream from the API response that we get from executing a SQL @@ -49,7 +50,6 @@ public class SnowflakeResultSetV1 extends SnowflakeBaseResultSet */ public SnowflakeResultSetV1(SFBaseResultSet sfBaseResultSet, Statement statement) throws SQLException { - super(statement); this.sfBaseResultSet = sfBaseResultSet; this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData()); @@ -101,6 +101,7 @@ public SnowflakeResultSetV1( SFBaseResultSet sfBaseResultSet, SnowflakeResultSetSerializableV1 resultSetSerializable) throws SQLException { super(resultSetSerializable); + this.sfBaseResultSet = sfBaseResultSet; this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData()); } @@ -271,16 +272,6 @@ public Object getObject(int columnIndex) throws SQLException { } } - public Array getArray(int columnIndex) throws SQLException { - raiseSQLExceptionIfResultSetIsClosed(); - try { - return sfBaseResultSet.getArray(columnIndex); - } catch (SFException ex) { - throw new SnowflakeSQLException( - ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams()); - } - } - public BigDecimal getBigDecimal(int columnIndex) throws SQLException { raiseSQLExceptionIfResultSetIsClosed(); try { diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index 3d2d36553..dacfbbfd5 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -29,7 +29,6 @@ public enum SnowflakeType { FIXED, INTEGER, OBJECT, - MAP, REAL, TEXT, TIME, diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index 6f361b6f1..73d07d6c4 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -16,7 +16,6 @@ import java.io.StringWriter; import java.lang.reflect.Field; import java.sql.Time; -import java.sql.Timestamp; import java.sql.Types; import java.time.Instant; import java.time.LocalDateTime; @@ -40,7 +39,6 @@ import net.snowflake.client.core.SnowflakeJdbcInternalApi; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.common.core.SnowflakeDateTimeFormat; import net.snowflake.common.core.SqlState; import net.snowflake.common.util.ClassUtil; import net.snowflake.common.util.FixedViewColumn; @@ -184,7 +182,7 @@ public static SnowflakeColumnMetadata extractColumnMetadata( String colSrcDatabase = colNode.path("database").asText(); String colSrcSchema = colNode.path("schema").asText(); String colSrcTable = colNode.path("table").asText(); - List fieldsMetadata = getFieldMetadata(jdbcTreatDecimalAsInt, colNode); + List fieldsMetadata = getFieldMetadata(fixedColType, colNode); boolean isAutoIncrement = colNode.path("isAutoIncrement").asBoolean(); @@ -275,11 +273,10 @@ static ColumnTypeInfo getSnowflakeType( case ARRAY: columnTypeInfo = - new ColumnTypeInfo(Types.ARRAY, defaultIfNull(extColTypeName, "ARRAY"), baseType); + new ColumnTypeInfo(Types.VARCHAR, defaultIfNull(extColTypeName, "ARRAY"), baseType); break; case OBJECT: - case MAP: int targetType = "GEOGRAPHY".equals(extColTypeName) || "GEOMETRY".equals(extColTypeName) ? Types.VARCHAR @@ -332,8 +329,8 @@ private static String defaultIfNull(String extColTypeName, String defaultValue) return Optional.ofNullable(extColTypeName).orElse(defaultValue); } - static List createFieldsMetadata( - ArrayNode fieldsJson, boolean jdbcTreatDecimalAsInt) throws SnowflakeSQLLoggedException { + static List createFieldsMetadata(ArrayNode fieldsJson, int fixedColType) + throws SnowflakeSQLLoggedException { List fields = new ArrayList<>(); for (JsonNode node : fieldsJson) { String colName = node.path("name").asText(); @@ -343,8 +340,7 @@ static List createFieldsMetadata( boolean nullable = node.path("nullable").asBoolean(); int length = node.path("length").asInt(); boolean fixed = node.path("fixed").asBoolean(); - int fixedColType = jdbcTreatDecimalAsInt && scale == 0 ? Types.BIGINT : Types.DECIMAL; - List internalFields = getFieldMetadata(jdbcTreatDecimalAsInt, node); + List internalFields = getFieldMetadata(fixedColType, node); JsonNode outputType = node.path("outputType"); JsonNode extColTypeNameNode = node.path("extTypeName"); String extColTypeName = null; @@ -370,11 +366,11 @@ static List createFieldsMetadata( return fields; } - private static List getFieldMetadata(boolean jdbcTreatDecimalAsInt, JsonNode node) + private static List getFieldMetadata(int fixedColType, JsonNode node) throws SnowflakeSQLLoggedException { if (!node.path("fields").isEmpty()) { ArrayNode internalFieldsJson = (ArrayNode) node.path("fields"); - return createFieldsMetadata(internalFieldsJson, jdbcTreatDecimalAsInt); + return createFieldsMetadata(internalFieldsJson, fixedColType); } else { return new ArrayList<>(); } @@ -764,39 +760,6 @@ public static Time getTimeInSessionTimezone(Long time, int nanos) { return ts; } - /** - * Helper function to convert system properties to boolean - * - * @param columnSubType column subtype value - * @param value value to convert - * @param session session object - * @return converted Timestamp object - */ - @SnowflakeJdbcInternalApi - public static Timestamp getTimestampFromType( - int columnSubType, String value, SFBaseSession session) { - if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { - return getTimestampFromFormat("TIMESTAMP_LTZ_OUTPUT_FORMAT", value, session); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ - || columnSubType == Types.TIMESTAMP) { - return getTimestampFromFormat("TIMESTAMP_NTZ_OUTPUT_FORMAT", value, session); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { - return getTimestampFromFormat("TIMESTAMP_TZ_OUTPUT_FORMAT", value, session); - } else { - return null; - } - } - - private static Timestamp getTimestampFromFormat( - String format, String value, SFBaseSession session) { - String rawFormat = (String) session.getCommonParameters().get(format); - if (rawFormat == null || rawFormat.equals("")) { - rawFormat = (String) session.getCommonParameters().get("TIMESTAMP_OUTPUT_FORMAT"); - } - SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat); - return formatter.parse(value).getTimestamp(); - } - /** * Helper function to convert system properties to boolean * diff --git a/src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java b/src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java deleted file mode 100644 index 37e6aab3e..000000000 --- a/src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2012-2024 Snowflake Computing Inc. All right reserved. - */ -package net.snowflake.client.util; - -import net.snowflake.client.core.SFException; -import net.snowflake.client.core.SnowflakeJdbcInternalApi; - -/** Functional interface used to convert json data to expected type */ -@SnowflakeJdbcInternalApi -@FunctionalInterface -public interface JsonStringToTypeConverter { - T convert(String string) throws SFException; -} diff --git a/src/test/java/net/snowflake/client/TestUtil.java b/src/test/java/net/snowflake/client/TestUtil.java index 1f782ec1f..fa53f4c58 100644 --- a/src/test/java/net/snowflake/client/TestUtil.java +++ b/src/test/java/net/snowflake/client/TestUtil.java @@ -116,13 +116,13 @@ public static void withSchema(Statement statement, String schemaName, ThrowingRu * @param action action to execute when schema was created * @throws Exception when any error occurred */ - public static void withRandomSchema( - Statement statement, ThrowingConsumer action) throws Exception { + public static void withRandomSchema(Statement statement, ThrowingConsumer action) + throws Exception { String customSchema = GENERATED_SCHEMA_PREFIX + SnowflakeUtil.randomAlphaNumeric(5).toUpperCase(); try { statement.execute("CREATE OR REPLACE SCHEMA " + customSchema); - action.accept(customSchema); + action.call(customSchema); } finally { statement.execute("DROP SCHEMA " + customSchema); } diff --git a/src/test/java/net/snowflake/client/ThrowingConsumer.java b/src/test/java/net/snowflake/client/ThrowingConsumer.java index d5a47cd5e..8b6f8c001 100644 --- a/src/test/java/net/snowflake/client/ThrowingConsumer.java +++ b/src/test/java/net/snowflake/client/ThrowingConsumer.java @@ -1,6 +1,6 @@ package net.snowflake.client; @FunctionalInterface -public interface ThrowingConsumer { - void accept(A parameter) throws T; +public interface ThrowingConsumer { + void call(T parameter) throws Exception; } diff --git a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java index c763606fe..65163a938 100644 --- a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java @@ -50,7 +50,6 @@ import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.telemetry.Telemetry; import net.snowflake.client.jdbc.telemetry.TelemetryData; -import net.snowflake.common.core.SFBinaryFormat; import net.snowflake.common.core.SnowflakeDateTimeFormat; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -882,21 +881,6 @@ public ResultSet createResultSet(String queryID, Statement statement) throws SQL @Override public SnowflakeBaseResultSet createResultSet(SFBaseResultSet resultSet, Statement statement) throws SQLException { - Converters convertes = - new Converters( - null, - new SFSession(), - 0, - false, - false, - false, - false, - SFBinaryFormat.BASE64, - null, - null, - null, - null, - null); return new SnowflakeResultSetV1(resultSet, statement); } diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java index 2535a6579..539784120 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java @@ -117,6 +117,7 @@ private void checkFeatureNotSupportedException(ResultSet resultSet) throws SQLEx expectFeatureNotSupportedException(() -> resultSet.getObject(1, Collections.emptyMap())); expectFeatureNotSupportedException(() -> resultSet.getRef(1)); expectFeatureNotSupportedException(() -> resultSet.getBlob(1)); + expectFeatureNotSupportedException(() -> resultSet.getArray(1)); expectFeatureNotSupportedException(() -> resultSet.getURL(1)); expectFeatureNotSupportedException(() -> resultSet.getRowId(1)); expectFeatureNotSupportedException(() -> resultSet.getNClob(1)); diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java index 830f940cb..2ec486ef6 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java @@ -18,14 +18,10 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.List; -import java.util.Map; import net.snowflake.client.ConditionalIgnoreRule; import net.snowflake.client.RunningOnGithubAction; -import net.snowflake.client.ThrowingConsumer; import net.snowflake.client.category.TestCategoryStructuredType; import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; -import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -51,12 +47,6 @@ public Connection init() throws SQLException { return conn; } - @Before - public void clean() throws Exception { - SnowflakeObjectTypeFactories.unregister(SimpleClass.class); - SnowflakeObjectTypeFactories.unregister(AllTypesClass.class); - } - // TODO Structured types feature exists only on QA environments @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) @@ -75,13 +65,17 @@ public void testMapStructToObjectWithReflection() throws SQLException { private void testMapJson(boolean registerFactory) throws SQLException { if (registerFactory) { SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(SimpleClass.class); + } + try (Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = + statement.executeQuery("select {'string':'a'}::OBJECT(string VARCHAR)"); ) { + resultSet.next(); + SimpleClass object = resultSet.getObject(1, SimpleClass.class); + assertEquals("a", object.getString()); } - withFirstRow( - "select {'string':'a'}::OBJECT(string VARCHAR)", - (resultSet) -> { - SimpleClass object = resultSet.getObject(1, SimpleClass.class); - assertEquals("a", object.getString()); - }); } // TODO Structured types feature exists only on QA environments @@ -141,12 +135,12 @@ private void testMapAllTypes(boolean registerFactory) throws SQLException { resultSet.next(); AllTypesClass object = resultSet.getObject(1, AllTypesClass.class); assertEquals("a", object.getString()); - assertEquals(new Byte("1"), object.getB()); - assertEquals(Short.valueOf("2"), object.getS()); - assertEquals(Integer.valueOf(3), object.getI()); - assertEquals(Long.valueOf(4), object.getL()); - assertEquals(Float.valueOf(1.1f), object.getF(), 0.01); - assertEquals(Double.valueOf(2.2), object.getD(), 0.01); + assertEquals(1, (long) object.getB()); + assertEquals(2, (long) object.getS()); + assertEquals(3, (long) object.getI()); + assertEquals(4, (long) object.getL()); + assertEquals(1.1, (double) object.getF(), 0.01); + assertEquals(2.2, (double) object.getD(), 0.01); assertEquals(BigDecimal.valueOf(3.3), object.getBd()); assertEquals( Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), object.getTimestampLtz()); @@ -164,254 +158,19 @@ private void testMapAllTypes(boolean registerFactory) throws SQLException { } } - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapJsonToMap() throws SQLException { - withFirstRow( - "SELECT OBJECT_CONSTRUCT('string','a','string2',1)", - (resultSet) -> { - Map map = resultSet.getObject(1, Map.class); - assertEquals("a", map.get("string")); - assertEquals(1, map.get("string2")); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testReturnAsArrayOfSqlData() throws SQLException { - SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); - withFirstRow( - "SELECT ARRAY_CONSTRUCT({'string':'one'}, {'string':'two'}, {'string':'three'})::ARRAY(OBJECT(string VARCHAR))", - (resultSet) -> { - SimpleClass[] resultArray = - resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, SimpleClass.class); - assertEquals("one", resultArray[0].getString()); - assertEquals("two", resultArray[1].getString()); - assertEquals("three", resultArray[2].getString()); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testReturnAsMap() throws SQLException { - SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); - withFirstRow( - "select {'x':{'string':'one'},'y':{'string':'two'},'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));", - (resultSet) -> { - Map map = - resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, SimpleClass.class); - assertEquals("one", map.get("x").getString()); - assertEquals("two", map.get("y").getString()); - assertEquals("three", map.get("z").getString()); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testReturnAsList() throws SQLException { - SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); - withFirstRow( - "select [{'string':'one'},{'string': 'two'}]::ARRAY(OBJECT(string varchar))", - (resultSet) -> { - List map = - resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, SimpleClass.class); - assertEquals("one", map.get(0).getString()); - assertEquals("two", map.get(1).getString()); - }); - } - // TODO Structured types feature exists only on QA environments @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapStructsFromChunks() throws SQLException { - withFirstRow( - "select {'string':'a'}::OBJECT(string VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))", - (resultSet) -> { - while (resultSet.next()) { - SimpleClass object = resultSet.getObject(1, SimpleClass.class); - assertEquals("a", object.getString()); - } - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapIntegerArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(INTEGER)", - (resultSet) -> { - Long[] resultArray = (Long[]) resultSet.getArray(1).getArray(); - assertEquals(Long.valueOf(10), resultArray[0]); - assertEquals(Long.valueOf(20), resultArray[1]); - assertEquals(Long.valueOf(30), resultArray[2]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapFixedToLongArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(SMALLINT)", - (resultSet) -> { - Long[] resultArray = (Long[]) resultSet.getArray(1).getArray(); - assertEquals(Long.valueOf("10"), resultArray[0]); - assertEquals(Long.valueOf("20"), resultArray[1]); - assertEquals(Long.valueOf("30"), resultArray[2]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapDecimalArray() throws SQLException { - // when: jdbc_treat_decimal_as_int=true scale=0 try (Connection connection = init(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery( - "SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,0))"); ) { - resultSet.next(); - Long[] resultArray = (Long[]) resultSet.getArray(1).getArray(); - assertEquals(resultArray[0], Long.valueOf(10)); - assertEquals(resultArray[1], Long.valueOf(20)); - assertEquals(resultArray[2], Long.valueOf(30)); - } - - // when: jdbc_treat_decimal_as_int=true scale=2 - try (Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet resultSet = - statement.executeQuery( - "SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,2))"); ) { - resultSet.next(); - BigDecimal[] resultArray2 = (BigDecimal[]) resultSet.getArray(1).getArray(); - assertEquals(BigDecimal.valueOf(10.2), resultArray2[0]); - assertEquals(BigDecimal.valueOf(20.02), resultArray2[1]); - assertEquals(BigDecimal.valueOf(30), resultArray2[2]); - } - - // when: jdbc_treat_decimal_as_int=false scale=0 - try (Connection connection = init(); - Statement statement = connection.createStatement(); ) { - statement.execute("alter session set jdbc_treat_decimal_as_int = false"); - try (ResultSet resultSet = - statement.executeQuery("SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,0))")) { - resultSet.next(); - BigDecimal[] resultArray = (BigDecimal[]) resultSet.getArray(1).getArray(); - assertEquals(BigDecimal.valueOf(10), resultArray[0]); - assertEquals(BigDecimal.valueOf(20), resultArray[1]); - assertEquals(BigDecimal.valueOf(30), resultArray[2]); + "select {'string':'a'}::OBJECT(string VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); ) { + while (resultSet.next()) { + SimpleClass object = resultSet.getObject(1, SimpleClass.class); + assertEquals("a", object.getString()); } } } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapVarcharArray() throws SQLException { - withFirstRow( - "SELECT 'text', ARRAY_CONSTRUCT('10', '20','30')::ARRAY(VARCHAR)", - (resultSet) -> { - String t = resultSet.getString(1); - String[] resultArray = (String[]) resultSet.getArray(2).getArray(); - assertEquals("10", resultArray[0]); - assertEquals("20", resultArray[1]); - assertEquals("30", resultArray[2]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapDatesArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(to_date('2023-12-24', 'YYYY-MM-DD'), to_date('2023-12-25', 'YYYY-MM-DD'))::ARRAY(DATE)", - (resultSet) -> { - Date[] resultArray = (Date[]) resultSet.getArray(1).getArray(); - assertEquals(Date.valueOf(LocalDate.of(2023, 12, 24)), resultArray[0]); - assertEquals(Date.valueOf(LocalDate.of(2023, 12, 25)), resultArray[1]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapTimeArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(to_time('15:39:20.123'), to_time('15:39:20.123'))::ARRAY(TIME)", - (resultSet) -> { - Time[] resultArray = (Time[]) resultSet.getArray(1).getArray(); - assertEquals(Time.valueOf(LocalTime.of(15, 39, 20)), resultArray[0]); - assertEquals(Time.valueOf(LocalTime.of(15, 39, 20)), resultArray[1]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapTimestampArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(TO_TIMESTAMP_NTZ('2021-12-23 09:44:44'), TO_TIMESTAMP_NTZ('2021-12-24 09:55:55'))::ARRAY(TIMESTAMP)", - (resultSet) -> { - Timestamp[] resultArray = (Timestamp[]) resultSet.getArray(1).getArray(); - assertEquals( - Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 10, 44, 44)), resultArray[0]); - assertEquals( - Timestamp.valueOf(LocalDateTime.of(2021, 12, 24, 10, 55, 55)), resultArray[1]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapBooleanArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(true,false)::ARRAY(BOOLEAN)", - (resultSet) -> { - Boolean[] resultArray = (Boolean[]) resultSet.getArray(1).getArray(); - assertEquals(true, resultArray[0]); - assertEquals(false, resultArray[1]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapBinaryArray() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(TO_BINARY('616263', 'HEX'),TO_BINARY('616263', 'HEX'))::ARRAY(BINARY)", - (resultSet) -> { - Byte[][] resultArray = (Byte[][]) resultSet.getArray(1).getArray(); - assertArrayEquals(new Byte[] {'a', 'b', 'c'}, resultArray[0]); - assertArrayEquals(new Byte[] {'a', 'b', 'c'}, resultArray[1]); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapArrayOfStructToMap() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2} )::ARRAY(OBJECT(x VARCHAR, y INTEGER))", - (resultSet) -> { - Map[] resultArray = (Map[]) resultSet.getArray(1).getArray(); - assertEquals("{x=abc, y=1}", resultArray[0].toString()); - assertEquals("{x=def, y=2}", resultArray[1].toString()); - }); - } - - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testMapArrayOfArrays() throws SQLException { - withFirstRow( - "SELECT ARRAY_CONSTRUCT(ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2}) )::ARRAY(ARRAY(OBJECT(x VARCHAR, y INTEGER)))", - (resultSet) -> { - Map[][] resultArray = (Map[][]) resultSet.getArray(1).getArray(); - assertEquals("{x=abc, y=1}", resultArray[0][0].toString()); - assertEquals("{x=def, y=2}", resultArray[0][1].toString()); - }); - } - - private void withFirstRow(String sqlText, ThrowingConsumer consumer) - throws SQLException { - try (Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(sqlText); ) { - assertTrue(rs.next()); - consumer.accept(rs); - } - } - ; } From 6c66fa3b838a3836e1d4da5876b4d26c90c8219d Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:28:52 -0700 Subject: [PATCH 5/9] Revert "SNOW-1168175 Update CHANGELOG.rst (#1668)" This reverts commit 7d7678ac4dc4f39471668382b5d13ae00f1a2896. --- CHANGELOG.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 199e5cedd..f3e0c7747 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,59 +1,59 @@ **JDBC Driver 3.15.0** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.5** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.4** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.3** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.2** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.1** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.14.0** -- \||Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \||Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.33** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.32** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.31** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.30** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.29** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.28** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.27** -- \|| Please Refer to Release Notes at https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc +- \|| Please Refer to Release Notes at https://community.snowflake.com/s/article/JDBC-Driver-Release-Notes **JDBC Driver 3.13.26** From 58ceaf24836afcd36fd031975f56103481f64cd4 Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:29:07 -0700 Subject: [PATCH 6/9] Revert "SNOW-1203558 Fix geo types values (#1663)" This reverts commit 5bb09bff36222bb66ce08c9b105faa071c3d9af4. --- src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java | 6 +----- .../net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index 73d07d6c4..ebe8a9eac 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -277,12 +277,8 @@ static ColumnTypeInfo getSnowflakeType( break; case OBJECT: - int targetType = - "GEOGRAPHY".equals(extColTypeName) || "GEOMETRY".equals(extColTypeName) - ? Types.VARCHAR - : Types.STRUCT; columnTypeInfo = - new ColumnTypeInfo(targetType, defaultIfNull(extColTypeName, "OBJECT"), baseType); + new ColumnTypeInfo(Types.STRUCT, defaultIfNull(extColTypeName, "OBJECT"), baseType); break; case VARIANT: diff --git a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java index b7cdb839d..39f610a2c 100644 --- a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java @@ -841,7 +841,7 @@ public void testGeoOutputTypes() throws Throwable { regularStatement, false, "geoJson", "OBJECT", "java.lang.String", Types.STRUCT); testGeoOutputTypeSingle( - regularStatement, true, "geoJson", "GEOGRAPHY", "java.lang.String", Types.VARCHAR); + regularStatement, true, "geoJson", "GEOGRAPHY", "java.lang.String", Types.STRUCT); testGeoOutputTypeSingle( regularStatement, false, "wkt", "VARCHAR", "java.lang.String", Types.VARCHAR); @@ -989,10 +989,10 @@ public void testGeometryOutputTypes() throws Throwable { "insert into t_geo2 values ('POINT(0 0)'), ('LINESTRING(1 1, 2 2)')"); testGeometryOutputTypeSingle( - regularStatement, true, "geoJson", "GEOMETRY", "java.lang.String", Types.VARCHAR); + regularStatement, true, "geoJson", "GEOMETRY", "java.lang.String", Types.STRUCT); testGeometryOutputTypeSingle( - regularStatement, true, "wkt", "GEOMETRY", "java.lang.String", Types.VARCHAR); + regularStatement, true, "wkt", "GEOMETRY", "java.lang.String", Types.STRUCT); } finally { if (regularStatement != null) { From 8b7817c646ccd9ad355d2ea3a42e5fb64ea6d29e Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:29:40 -0700 Subject: [PATCH 7/9] Revert "SNOW-1196041: Ignore default credentials in GCS client (#1656)" This reverts commit 730fbbf659282d53feb95095ad3c6f044b6678a7. --- parent-pom.xml | 10 - .../snowflake/client/core/SFBaseSession.java | 11 - .../net/snowflake/client/core/SFSession.java | 5 - .../client/core/SFSessionProperty.java | 4 +- .../snowflake/client/jdbc/SnowflakeUtil.java | 20 +- .../cloud/storage/SnowflakeGCSClient.java | 25 +-- .../cloud/storage/StorageClientFactory.java | 2 +- .../client/jdbc/ConnectionLatestIT.java | 4 +- .../client/jdbc/SnowflakeDriverLatestIT.java | 204 ++++++++---------- thin_public_pom.xml | 6 - 10 files changed, 97 insertions(+), 194 deletions(-) diff --git a/parent-pom.xml b/parent-pom.xml index cd01b5a79..9a1882c2e 100644 --- a/parent-pom.xml +++ b/parent-pom.xml @@ -38,7 +38,6 @@ 1.5.4 0.9.5.4 2.22.0 - 1.19.0 2.21.0 2.22.6 2.10.1 @@ -162,11 +161,6 @@ proto-google-common-protos ${google.api.grpc.version} - - com.google.auth - google-auth-library-oauth2-http - ${google.auth.library.oauth2.http.version} - com.google.cloud google-cloud-core @@ -520,10 +514,6 @@ com.google.api gax - - com.google.auth - google-auth-library-oauth2-http - com.google.cloud google-cloud-core diff --git a/src/main/java/net/snowflake/client/core/SFBaseSession.java b/src/main/java/net/snowflake/client/core/SFBaseSession.java index 5c0e20fcc..364a45ad9 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseSession.java +++ b/src/main/java/net/snowflake/client/core/SFBaseSession.java @@ -137,9 +137,6 @@ public abstract class SFBaseSession { // we need to allow for it to maintain backwards compatibility. private boolean enablePatternSearch = true; - /** Disable lookup for default credentials by GCS library */ - private boolean disableGcsDefaultCredentials = false; - private Map commonParameters; protected SFBaseSession(SFConnectionHandler sfConnectionHandler) { @@ -738,14 +735,6 @@ public void setEnablePatternSearch(boolean enablePatternSearch) { this.enablePatternSearch = enablePatternSearch; } - public boolean getDisableGcsDefaultCredentials() { - return disableGcsDefaultCredentials; - } - - public void setDisableGcsDefaultCredentials(boolean disableGcsDefaultCredentials) { - this.disableGcsDefaultCredentials = disableGcsDefaultCredentials; - } - public int getClientResultChunkSize() { return clientResultChunkSize; } diff --git a/src/main/java/net/snowflake/client/core/SFSession.java b/src/main/java/net/snowflake/client/core/SFSession.java index 75d5e9600..20850b4f0 100644 --- a/src/main/java/net/snowflake/client/core/SFSession.java +++ b/src/main/java/net/snowflake/client/core/SFSession.java @@ -469,11 +469,6 @@ public void addSFSessionProperty(String propertyName, Object propertyValue) thro setEnablePatternSearch(getBooleanValue(propertyValue)); } break; - case DISABLE_GCS_DEFAULT_CREDENTIALS: - if (propertyValue != null) { - setDisableGcsDefaultCredentials(getBooleanValue(propertyValue)); - } - break; default: break; diff --git a/src/main/java/net/snowflake/client/core/SFSessionProperty.java b/src/main/java/net/snowflake/client/core/SFSessionProperty.java index d99ae7f22..0d42edfef 100644 --- a/src/main/java/net/snowflake/client/core/SFSessionProperty.java +++ b/src/main/java/net/snowflake/client/core/SFSessionProperty.java @@ -78,9 +78,7 @@ public enum SFSessionProperty { RETRY_TIMEOUT("retryTimeout", false, Integer.class), - ENABLE_PATTERN_SEARCH("enablePatternSearch", false, Boolean.class), - - DISABLE_GCS_DEFAULT_CREDENTIALS("disableGcsDefaultCredentials", false, Boolean.class); + ENABLE_PATTERN_SEARCH("enablePatternSearch", false, Boolean.class); // property key in string private String propertyKey; diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index ebe8a9eac..247831e0e 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -36,7 +36,6 @@ import net.snowflake.client.core.OCSPMode; import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.SFSessionProperty; -import net.snowflake.client.core.SnowflakeJdbcInternalApi; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; @@ -51,7 +50,7 @@ */ public class SnowflakeUtil { - private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeUtil.class); + static final SFLogger logger = SFLoggerFactory.getLogger(RestRequest.class); /** Additional data types not covered by standard JDBC */ public static final int EXTRA_TYPES_TIMESTAMP_LTZ = 50000; @@ -755,21 +754,4 @@ public static Time getTimeInSessionTimezone(Long time, int nanos) { ts.setTime(c.getTimeInMillis()); return ts; } - - /** - * Helper function to convert system properties to boolean - * - * @param systemProperty name of the system property - * @param defaultValue default value used - * @return the value of the system property as boolean, else the default value - */ - @SnowflakeJdbcInternalApi - public static boolean convertSystemPropertyToBooleanValue( - String systemProperty, boolean defaultValue) { - String systemPropertyValue = systemGetProperty(systemProperty); - if (systemPropertyValue != null) { - return Boolean.parseBoolean(systemPropertyValue); - } - return defaultValue; - } } diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeGCSClient.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeGCSClient.java index 61c31b3ab..25b3bc9dc 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeGCSClient.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeGCSClient.java @@ -12,8 +12,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.gax.paging.Page; import com.google.api.gax.rpc.FixedHeaderProvider; -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.storage.Blob; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; @@ -45,7 +43,6 @@ import net.snowflake.client.core.ObjectMapperFactory; import net.snowflake.client.core.SFSession; import net.snowflake.client.core.SFSessionProperty; -import net.snowflake.client.core.SnowflakeJdbcInternalApi; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.FileBackedOutputStream; import net.snowflake.client.jdbc.MatDesc; @@ -77,10 +74,6 @@ * @author ppaulus */ public class SnowflakeGCSClient implements SnowflakeStorageClient { - @SnowflakeJdbcInternalApi - public static final String DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME = - "net.snowflake.jdbc.disableGcsDefaultCredentials"; - private static final String GCS_ENCRYPTIONDATAPROP = "encryptiondata"; private static final String localFileSep = systemGetProperty("file.separator"); private static final String GCS_METADATA_PREFIX = "x-goog-meta-"; @@ -1207,19 +1200,13 @@ private void setupGCSClient( try { String accessToken = (String) stage.getCredentials().get("GCS_ACCESS_TOKEN"); if (accessToken != null) { - // We are authenticated with an oauth access token. - StorageOptions.Builder builder = StorageOptions.newBuilder(); - if (areDisabledGcsDefaultCredentials(session)) { - logger.debug( - "Adding explicit credentials to avoid default credential lookup by the GCS client"); - builder.setCredentials(GoogleCredentials.create(new AccessToken(accessToken, null))); - } - // Using GoogleCredential with access token will cause IllegalStateException when the token // is expired and trying to refresh, which cause error cannot be caught. Instead, set a // header so we can caught the error code. + + // We are authenticated with an oauth access token. this.gcsClient = - builder + StorageOptions.newBuilder() .setHeaderProvider( FixedHeaderProvider.create("Authorization", "Bearer " + accessToken)) .build() @@ -1247,12 +1234,6 @@ private void setupGCSClient( } } - private static boolean areDisabledGcsDefaultCredentials(SFSession session) { - return session != null && session.getDisableGcsDefaultCredentials() - || SnowflakeUtil.convertSystemPropertyToBooleanValue( - DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME, false); - } - private static boolean isSuccessStatusCode(int code) { return code < 300 && code >= 200; } diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageClientFactory.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageClientFactory.java index ef97c9508..bfa2e3451 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageClientFactory.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageClientFactory.java @@ -22,7 +22,7 @@ */ public class StorageClientFactory { - private static final SFLogger logger = SFLoggerFactory.getLogger(StorageClientFactory.class); + private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeS3Client.class); private static StorageClientFactory factory; diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java index b9a406176..15d8b2f80 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java @@ -403,10 +403,10 @@ public void testQueryStatusErrorMessageAndErrorCodeChangeOnAsyncQuery() throws S SnowflakeResultSet sfResultSet = rs1.unwrap(SnowflakeResultSet.class); // status should change state to RUNNING and then to SUCCESS await() - .atMost(Duration.ofSeconds(10)) + .atMost(Duration.ofSeconds(5)) .until(() -> sfResultSet.getStatusV2().getStatus(), equalTo(QueryStatus.RUNNING)); await() - .atMost(Duration.ofSeconds(50)) + .atMost(Duration.ofSeconds(5)) .until(() -> sfResultSet.getStatusV2().getStatus(), equalTo(QueryStatus.SUCCESS)); } } diff --git a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java index 39f610a2c..89d5c0c06 100644 --- a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverLatestIT.java @@ -8,7 +8,6 @@ import static net.snowflake.client.jdbc.SnowflakeDriverIT.findFile; import static net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.mapper; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -47,9 +46,7 @@ import net.snowflake.client.core.Constants; import net.snowflake.client.core.OCSPMode; import net.snowflake.client.core.SFSession; -import net.snowflake.client.core.SFSessionProperty; import net.snowflake.client.core.SFStatement; -import net.snowflake.client.jdbc.cloud.storage.SnowflakeGCSClient; import net.snowflake.client.jdbc.cloud.storage.SnowflakeStorageClient; import net.snowflake.client.jdbc.cloud.storage.StageInfo; import net.snowflake.client.jdbc.cloud.storage.StorageClientFactory; @@ -1108,67 +1105,58 @@ private void testGeometryMetadataSingle( @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testPutGetGcsDownscopedCredential() throws Throwable { + Connection connection = null; + Statement statement = null; Properties paramProperties = new Properties(); paramProperties.put("GCS_USE_DOWNSCOPED_CREDENTIAL", true); - try (Connection connection = getConnection("gcpaccount", paramProperties); - Statement statement = connection.createStatement()) { - putAndGetFile(statement); - } - } + try { + connection = getConnection("gcpaccount", paramProperties); - /** Added in > 3.15.0 */ - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void testPutGetGcsDownscopedCredentialWithDisabledDefaultCredentials() throws Throwable { - Properties paramProperties = new Properties(); - paramProperties.put("GCS_USE_DOWNSCOPED_CREDENTIAL", true); - paramProperties.put(SFSessionProperty.DISABLE_GCS_DEFAULT_CREDENTIALS.getPropertyKey(), true); - try (Connection connection = getConnection("gcpaccount", paramProperties); - Statement statement = connection.createStatement()) { - putAndGetFile(statement); - } - } + statement = connection.createStatement(); - private void putAndGetFile(Statement statement) throws Throwable { - String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE_2); + String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE_2); - File destFolder = tmpFolder.newFolder(); - String destFolderCanonicalPath = destFolder.getCanonicalPath(); - String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator; + File destFolder = tmpFolder.newFolder(); + String destFolderCanonicalPath = destFolder.getCanonicalPath(); + String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator; - try { - statement.execute("CREATE OR REPLACE STAGE testPutGet_stage"); + try { + statement.execute("CREATE OR REPLACE STAGE testPutGet_stage"); - assertTrue( - "Failed to put a file", - statement.execute("PUT file://" + sourceFilePath + " @testPutGet_stage")); + assertTrue( + "Failed to put a file", + statement.execute("PUT file://" + sourceFilePath + " @testPutGet_stage")); - findFile(statement, "ls @testPutGet_stage/"); + findFile(statement, "ls @testPutGet_stage/"); - // download the file we just uploaded to stage - assertTrue( - "Failed to get a file", - statement.execute( - "GET @testPutGet_stage 'file://" + destFolderCanonicalPath + "' parallel=8")); + // download the file we just uploaded to stage + assertTrue( + "Failed to get a file", + statement.execute( + "GET @testPutGet_stage 'file://" + destFolderCanonicalPath + "' parallel=8")); - // Make sure that the downloaded file exists, it should be gzip compressed - File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + ".gz"); - assert (downloaded.exists()); + // Make sure that the downloaded file exists, it should be gzip compressed + File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + ".gz"); + assert (downloaded.exists()); - Process p = - Runtime.getRuntime() - .exec("gzip -d " + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + ".gz"); - p.waitFor(); + Process p = + Runtime.getRuntime() + .exec("gzip -d " + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + ".gz"); + p.waitFor(); - File original = new File(sourceFilePath); - File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2); - System.out.println( - "Original file: " + original.getAbsolutePath() + ", size: " + original.length()); - System.out.println( - "Unzipped file: " + unzipped.getAbsolutePath() + ", size: " + unzipped.length()); - assert (original.length() == unzipped.length()); + File original = new File(sourceFilePath); + File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2); + System.out.println( + "Original file: " + original.getAbsolutePath() + ", size: " + original.length()); + System.out.println( + "Unzipped file: " + unzipped.getAbsolutePath() + ", size: " + unzipped.length()); + assert (original.length() == unzipped.length()); + } finally { + statement.execute("DROP STAGE IF EXISTS testGetPut_stage"); + statement.close(); + } } finally { - statement.execute("DROP STAGE IF EXISTS testGetPut_stage"); + closeSQLObjects(null, statement, connection); } } @@ -1509,7 +1497,7 @@ public void testAzureS3UploadStreamingIngestFileMetadata() throws Throwable { ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo(), 1, null, - /* session= */ null); + /*session = */ null); String location = ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo().getLocation(); @@ -1549,7 +1537,7 @@ public void testNoSpaceLeftOnDeviceException() throws SQLException { new SnowflakeFileTransferAgent(command, sfSession, sfStatement); StageInfo info = sfAgent.getStageInfo(); SnowflakeStorageClient client = - StorageClientFactory.getFactory().createClient(info, 1, null, /* session= */ null); + StorageClientFactory.getFactory().createClient(info, 1, null, /*session = */ null); client.handleStorageException( new StorageException( @@ -1625,72 +1613,58 @@ public void testUploadWithGCSPresignedUrlWithoutConnection() throws Throwable { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testUploadWithGCSDownscopedCredentialWithoutConnection() throws Throwable { - uploadWithGCSDownscopedCredentialWithoutConnection(); - } - - /** Added in > 3.15.0 */ - @Test - @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) - public void - testUploadWithGCSDownscopedCredentialAndDisabledGcsDefaultCredentialsWithoutConnection() - throws Throwable { - System.setProperty(SnowflakeGCSClient.DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME, "true"); - try { - uploadWithGCSDownscopedCredentialWithoutConnection(); - } finally { - System.clearProperty(SnowflakeGCSClient.DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME); - } - } - - private void uploadWithGCSDownscopedCredentialWithoutConnection() throws Throwable { + Connection connection = null; File destFolder = tmpFolder.newFolder(); String destFolderCanonicalPath = destFolder.getCanonicalPath(); - Properties paramProperties = new Properties(); - paramProperties.put("GCS_USE_DOWNSCOPED_CREDENTIAL", true); - try (Connection connection = getConnection("gcpaccount", paramProperties); - Statement statement = connection.createStatement(); ) { - try { - // create a stage to put the file in - statement.execute("CREATE OR REPLACE STAGE " + testStageName); + try { + Properties paramProperties = new Properties(); + paramProperties.put("GCS_USE_DOWNSCOPED_CREDENTIAL", true); + connection = getConnection("gcpaccount", paramProperties); + // connection = getConnection(null, paramProperties); + Statement statement = connection.createStatement(); - SFSession sfSession = connection.unwrap(SnowflakeConnectionV1.class).getSfSession(); + // create a stage to put the file in + statement.execute("CREATE OR REPLACE STAGE " + testStageName); - // Test put file with internal compression - String putCommand = "put file:///dummy/path/file1.gz @" + testStageName; - SnowflakeFileTransferAgent sfAgent = - new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession)); - List metadataList = sfAgent.getFileTransferMetadatas(); - assertEquals(1, metadataList.size()); - SnowflakeFileTransferMetadata oneMetadata = metadataList.get(0); - assertFalse(oneMetadata.isForOneFile()); - - // Upload multiple file with the same SnowflakeFileTransferMetadata - String[] fileNames = {TEST_DATA_FILE, TEST_DATA_FILE_2}; - for (String fileName : fileNames) { - String srcPath = getFullPathFileInResource(fileName); - try (InputStream inputStream = new FileInputStream(srcPath)) { - // upload file 1 - String targetFileName = fileName + ".gz"; - SnowflakeFileTransferAgent.uploadWithoutConnection( - SnowflakeFileTransferConfig.Builder.newInstance() - .setSnowflakeFileTransferMetadata(oneMetadata) - .setUploadStream(inputStream) - .setDestFileName(targetFileName) - .setRequireCompress(true) - .setNetworkTimeoutInMilli(0) - .setOcspMode(OCSPMode.FAIL_OPEN) - .build()); - assertTrue( - "Failed to get files with down-scoped token", - statement.execute( - "GET @" + testStageName + " 'file://" + destFolderCanonicalPath + "/'")); - assertTrue( - isFileContentEqual( - srcPath, false, destFolderCanonicalPath + "/" + targetFileName, true)); - } - } - } finally { - statement.execute("DROP STAGE if exists " + testStageName); + SFSession sfSession = connection.unwrap(SnowflakeConnectionV1.class).getSfSession(); + + // Test put file with internal compression + String putCommand = "put file:///dummy/path/file1.gz @" + testStageName; + SnowflakeFileTransferAgent sfAgent = + new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession)); + List metadataList = sfAgent.getFileTransferMetadatas(); + assert (metadataList.size() == 1); + SnowflakeFileTransferMetadata oneMetadata = metadataList.get(0); + assert (!oneMetadata.isForOneFile()); + + // Upload multiple file with the same SnowflakeFileTransferMetadata + String[] fileNames = {TEST_DATA_FILE, TEST_DATA_FILE_2}; + for (String fileName : fileNames) { + String srcPath = getFullPathFileInResource(fileName); + InputStream inputStream = new FileInputStream(srcPath); + // upload file 1 + String targetFileName = fileName + ".gz"; + SnowflakeFileTransferAgent.uploadWithoutConnection( + SnowflakeFileTransferConfig.Builder.newInstance() + .setSnowflakeFileTransferMetadata(oneMetadata) + .setUploadStream(inputStream) + .setDestFileName(targetFileName) + .setRequireCompress(true) + .setNetworkTimeoutInMilli(0) + .setOcspMode(OCSPMode.FAIL_OPEN) + .build()); + assertTrue( + "Failed to get files with down-scoped token", + statement.execute( + "GET @" + testStageName + " 'file://" + destFolderCanonicalPath + "/'")); + assert (isFileContentEqual( + srcPath, false, destFolderCanonicalPath + "/" + targetFileName, true)); + inputStream.close(); + } + } finally { + if (connection != null) { + connection.createStatement().execute("DROP STAGE if exists " + testStageName); + connection.close(); } } } diff --git a/thin_public_pom.xml b/thin_public_pom.xml index 329dfe33a..0503b71df 100644 --- a/thin_public_pom.xml +++ b/thin_public_pom.xml @@ -44,7 +44,6 @@ 2.21.0 2.22.6 1.12.0 - 1.19.0 2.31.0 32.1.1-jre 1.43.3 @@ -145,11 +144,6 @@ gax ${google.gax.version} - - com.google.auth - google-auth-library-oauth2-http - ${google.auth.library.oauth2.http.version} - com.google.cloud google-cloud-core From e85e7e50402b79e1756b2e909dfa8ad517ef4abd Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 13 Mar 2024 17:32:12 -0700 Subject: [PATCH 8/9] Check-style fix --- src/test/java/net/snowflake/client/jdbc/RestRequestTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java b/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java index e2988e06e..ed6e165d1 100644 --- a/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java +++ b/src/test/java/net/snowflake/client/jdbc/RestRequestTest.java @@ -363,13 +363,13 @@ public void testExceptionAuthBasedTimeout() throws IOException { public void testExceptionAuthBasedTimeoutFor429ErrorCode() throws IOException { CloseableHttpClient client = mock(CloseableHttpClient.class); when(client.execute(any(HttpUriRequest.class))) - .thenAnswer((Answer) invocation -> retryLoginResponse()); + .thenAnswer((Answer) invocation -> retryLoginResponse()); try { execute(client, "login-request.com/?requestId=abcd-1234", 2, 1, 30000, true, false); } catch (SnowflakeSQLException ex) { assertThat( - ex.getErrorCode(), equalTo(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode())); + ex.getErrorCode(), equalTo(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode())); } } From 76e4ff7cfc02b77c9fa08acf7a0aae699b7f4e9b Mon Sep 17 00:00:00 2001 From: Jelena Furundzic Date: Wed, 3 Apr 2024 23:29:11 -0700 Subject: [PATCH 9/9] Added retry for the okta saml flow --- .../snowflake/client/core/SessionUtil.java | 16 ++++++++- .../snowflake/client/jdbc/RestRequest.java | 10 ++++++ .../snowflake/client/jdbc/ConnectionIT.java | 36 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index 4003cd823..88020f6f0 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -401,7 +401,21 @@ private static SFLoginOutput newSession( } } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { // okta authenticator v1 - tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); + while (true) { + try { + tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); + } catch (SnowflakeSQLException ex) { + // This error gets thrown if the okta request encountered a retry-able error that + // requires getting a new one-time token. + if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) { + logger.debug("Failed to get Okta SAML response. Retrying."); + continue; + } else { + throw ex; + } + } + break; + } } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { SessionUtilKeyPair s = new SessionUtilKeyPair( diff --git a/src/main/java/net/snowflake/client/jdbc/RestRequest.java b/src/main/java/net/snowflake/client/jdbc/RestRequest.java index 615b20894..fa7826664 100644 --- a/src/main/java/net/snowflake/client/jdbc/RestRequest.java +++ b/src/main/java/net/snowflake/client/jdbc/RestRequest.java @@ -399,6 +399,16 @@ public static CloseableHttpResponse execute( break; } + // If this was a request for an Okta one-time token that failed with a retry-able error, + // throw exception to renew the token before trying again. + if (String.valueOf(httpRequest.getURI()).contains("okta.com/api/v1/authn")) { + throw new SnowflakeSQLException( + ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT, + retryCount, + true, + elapsedMilliForTransientIssues / 1000); + } + // Make sure that any authenticator specific info that needs to be // updated get's updated before the next retry. Ex - JWT token // Check to see if customer set socket/connect timeout has been reached, diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java index 14b9f2d71..68d337aff 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java @@ -33,8 +33,10 @@ import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; +import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; @@ -387,6 +389,40 @@ public void testDataSourceOktaSerialization() throws Exception { con.close(); } + @Test + @Ignore + public void testDataSourceOktaGenerates429StatusCode() throws Exception { + // test with username/password authentication + // set up DataSource object and ensure connection works + Map params = getConnectionParameters(); + SnowflakeBasicDataSource ds = new SnowflakeBasicDataSource(); + ds.setServerName(params.get("host")); + ds.setSsl("on".equals(params.get("ssl"))); + ds.setAccount(params.get("account")); + ds.setPortNumber(Integer.parseInt(params.get("port"))); + ds.setUser(params.get("ssoUser")); + ds.setPassword(params.get("ssoPassword")); + ds.setAuthenticator(""); + Runnable r = + () -> { + try { + ds.getConnection(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }; + List threadList = new ArrayList<>(); + for (int i = 0; + i < 30; + ++i) { // https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use#http-429-errors + threadList.add(new Thread(r)); + } + threadList.forEach(Thread::start); + for (Thread thread : threadList) { + thread.join(); + } + } + @Test @ConditionalIgnore(condition = RunningOnGithubAction.class) public void testConnectUsingKeyPair() throws Exception {