diff --git a/README.rst b/README.rst index a9d3cacb2..faac38fd1 100644 --- a/README.rst +++ b/README.rst @@ -9,12 +9,22 @@ Snowflake JDBC Driver .. image:: http://img.shields.io/:license-Apache%202-brightgreen.svg :target: http://www.apache.org/licenses/LICENSE-2.0.txt - -.. image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc/badge.svg?style=plastic - :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/ - + Snowflake provides a JDBC type 4 driver that supports core functionality, allowing Java program to connect to Snowflake. +.. |maven-snowflake-jdbc| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc/badge.svg?style=plastic + :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/ + +.. |maven-snowflake-jdbc-fips| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc-fips/badge.svg?style=plastic + :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc-fips/ + +.. |maven-snowflake-jdbc-thin| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc-thin/badge.svg?style=plastic + :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc-thin/ + +- snowflake-jdbc (fat-jar): |maven-snowflake-jdbc| +- snowflake-jdbc-fips (FIPS compliant fat-jar): |maven-snowflake-jdbc-fips| +- snowflake-jdbc-thin (thin-jar): |maven-snowflake-jdbc-thin| + Prerequisites ============= @@ -45,7 +55,7 @@ or for FIPS compliant fat-jar {version} -or for experimental thin-jar +or for thin-jar .. code-block:: xml @@ -79,7 +89,7 @@ Build from Source Code ../mvnw org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file -Dfile=target/snowflake-jdbc-fips.jar -DpomFile=./public_pom.xml cd - -4. Build the experimental thin-jar and install it in local maven repository by running: +4. Build the thin-jar and install it in local maven repository by running: .. code-block:: bash diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index cbb92bcde..69195e8a4 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -26,7 +26,7 @@ import java.util.stream.Stream; import net.snowflake.client.core.arrow.ArrayConverter; import net.snowflake.client.core.arrow.ArrowVectorConverter; -import net.snowflake.client.core.arrow.StructConverter; +import net.snowflake.client.core.arrow.StructObjectWrapper; import net.snowflake.client.core.arrow.VarCharConverter; import net.snowflake.client.core.arrow.VectorTypeConverter; import net.snowflake.client.core.json.Converters; @@ -576,16 +576,19 @@ public Object getObject(int columnIndex) throws SFException { converter.setSessionTimeZone(sessionTimeZone); Object obj = converter.toObject(index); boolean isStructuredType = resultSetMetaData.isStructuredTypeColumn(columnIndex); - if (type == Types.STRUCT && isStructuredType) { - if (converter instanceof VarCharConverter) { - return createJsonSqlInput(columnIndex, obj); - } else if (converter instanceof StructConverter) { - return createArrowSqlInput(columnIndex, (Map) obj); + if (isVarcharConvertedStruct(type, isStructuredType, converter)) { + if (obj != null) { + return new StructObjectWrapper((String) obj, createJsonSqlInput(columnIndex, obj)); } } return obj; } + private boolean isVarcharConvertedStruct( + int type, boolean isStructuredType, ArrowVectorConverter converter) { + return type == Types.STRUCT && isStructuredType && converter instanceof VarCharConverter; + } + private Object createJsonSqlInput(int columnIndex, Object obj) throws SFException { try { if (obj == null) { @@ -605,15 +608,6 @@ private Object createJsonSqlInput(int columnIndex, Object obj) throws SFExceptio } } - private Object createArrowSqlInput(int columnIndex, Map input) - throws SFException { - if (input == null) { - return null; - } - return new ArrowSqlInput( - input, session, converters, resultSetMetaData.getColumnFields(columnIndex)); - } - @Override public Array getArray(int columnIndex) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); @@ -625,16 +619,19 @@ public Array getArray(int columnIndex) throws SFException { } if (converter instanceof VarCharConverter) { return getJsonArray((String) obj, columnIndex); - } else if (converter instanceof ArrayConverter) { - return getArrowArray((List) obj, columnIndex); - } else if (converter instanceof VectorTypeConverter) { - return getArrowArray((List) obj, columnIndex); + } else if (converter instanceof ArrayConverter || converter instanceof VectorTypeConverter) { + StructObjectWrapper structObjectWrapper = (StructObjectWrapper) obj; + return getArrowArray( + structObjectWrapper.getJsonString(), + (List) structObjectWrapper.getObject(), + columnIndex); } else { throw new SFException(queryId, ErrorCode.INVALID_STRUCT_DATA); } } - private SfSqlArray getArrowArray(List elements, int columnIndex) throws SFException { + private SfSqlArray getArrowArray(String text, List elements, int columnIndex) + throws SFException { try { List fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex); if (fieldMetadataList.size() != 1) { @@ -651,26 +648,31 @@ private SfSqlArray getArrowArray(List elements, int columnIndex) throws switch (columnType) { case Types.INTEGER: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.integerConverter(columnType)) .toArray(Integer[]::new)); case Types.SMALLINT: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.smallIntConverter(columnType)) .toArray(Short[]::new)); case Types.TINYINT: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.tinyIntConverter(columnType)) .toArray(Byte[]::new)); case Types.BIGINT: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.bigIntConverter(columnType)).toArray(Long[]::new)); case Types.DECIMAL: case Types.NUMERIC: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.bigDecimalConverter(columnType)) .toArray(BigDecimal[]::new)); @@ -678,35 +680,42 @@ private SfSqlArray getArrowArray(List elements, int columnIndex) throws case Types.VARCHAR: case Types.LONGNVARCHAR: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.varcharConverter(columnType, columnSubType, scale)) .toArray(String[]::new)); case Types.BINARY: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.bytesConverter(columnType, scale)) .toArray(Byte[][]::new)); case Types.FLOAT: case Types.REAL: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.floatConverter(columnType)).toArray(Float[]::new)); case Types.DOUBLE: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.doubleConverter(columnType)) .toArray(Double[]::new)); case Types.DATE: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.dateFromIntConverter(sessionTimeZone)) .toArray(Date[]::new)); case Types.TIME: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.timeFromIntConverter(scale)).toArray(Time[]::new)); case Types.TIMESTAMP: return new SfSqlArray( + text, columnSubType, mapAndConvert( elements, @@ -715,13 +724,16 @@ private SfSqlArray getArrowArray(List elements, int columnIndex) throws .toArray(Timestamp[]::new)); case Types.BOOLEAN: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, converters.booleanConverter(columnType)) .toArray(Boolean[]::new)); case Types.STRUCT: - return new SfSqlArray(columnSubType, mapAndConvert(elements, e -> e).toArray(Map[]::new)); + return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, e -> e).toArray(Map[]::new)); case Types.ARRAY: return new SfSqlArray( + text, columnSubType, mapAndConvert(elements, e -> ((List) e).stream().toArray(Map[]::new)) .toArray(Map[][]::new)); diff --git a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java index 71e56a515..c0b6256ad 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java @@ -273,7 +273,7 @@ protected SQLInput createJsonSqlInputForColumn( } @SnowflakeJdbcInternalApi - protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFException { + protected SfSqlArray getJsonArray(String arrayString, int columnIndex) throws SFException { try { List fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex); if (fieldMetadataList.size() != 1) { @@ -288,33 +288,38 @@ protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFExceptio int columnType = ColumnTypeHelper.getColumnType(columnSubType, session); int scale = fieldMetadata.getScale(); - ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(obj); + ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(arrayString); Iterator nodeElements = arrayNode.elements(); switch (columnType) { case Types.INTEGER: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().integerConverter(columnType)) .toArray(Integer[]::new)); case Types.SMALLINT: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().smallIntConverter(columnType)) .toArray(Short[]::new)); case Types.TINYINT: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().tinyIntConverter(columnType)) .toArray(Byte[]::new)); case Types.BIGINT: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().bigIntConverter(columnType)) .toArray(Long[]::new)); case Types.DECIMAL: case Types.NUMERIC: return new SfSqlArray( + arrayString, columnSubType, convertToFixedArray( getStream(nodeElements, getConverters().bigDecimalConverter(columnType)))); @@ -322,6 +327,7 @@ protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFExceptio case Types.VARCHAR: case Types.LONGNVARCHAR: return new SfSqlArray( + arrayString, columnSubType, getStream( nodeElements, @@ -329,32 +335,38 @@ protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFExceptio .toArray(String[]::new)); case Types.BINARY: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().bytesConverter(columnType, scale)) .toArray(Byte[][]::new)); case Types.FLOAT: case Types.REAL: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().floatConverter(columnType)) .toArray(Float[]::new)); case Types.DOUBLE: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().doubleConverter(columnType)) .toArray(Double[]::new)); case Types.DATE: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().dateStringConverter(session)) .toArray(Date[]::new)); case Types.TIME: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().timeFromStringConverter(session)) .toArray(Time[]::new)); case Types.TIMESTAMP: return new SfSqlArray( + arrayString, columnSubType, getStream( nodeElements, @@ -364,16 +376,19 @@ protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFExceptio .toArray(Timestamp[]::new)); case Types.BOOLEAN: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().booleanConverter(columnType)) .toArray(Boolean[]::new)); case Types.STRUCT: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().structConverter(OBJECT_MAPPER)) .toArray(Map[]::new)); case Types.ARRAY: return new SfSqlArray( + arrayString, columnSubType, getStream(nodeElements, getConverters().arrayConverter(OBJECT_MAPPER)) .toArray(Map[][]::new)); diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index c32a16424..04e8d3fba 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -15,6 +15,7 @@ import java.sql.Types; import java.util.List; import java.util.TimeZone; +import net.snowflake.client.core.arrow.StructObjectWrapper; import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.FieldMetadata; @@ -87,7 +88,7 @@ public Object getObject(int columnIndex) throws SFException { case Types.STRUCT: if (resultSetMetaData.isStructuredTypeColumn(columnIndex)) { - return getSqlInput((String) obj, columnIndex); + return new StructObjectWrapper((String) obj, getSqlInput((String) obj, columnIndex)); } else { throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } diff --git a/src/main/java/net/snowflake/client/core/SFSession.java b/src/main/java/net/snowflake/client/core/SFSession.java index c3708ea7e..59aac9d5b 100644 --- a/src/main/java/net/snowflake/client/core/SFSession.java +++ b/src/main/java/net/snowflake/client/core/SFSession.java @@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import net.snowflake.client.config.SFClientConfig; +import net.snowflake.client.core.auth.AuthenticatorType; import net.snowflake.client.jdbc.DefaultSFConnectionHandler; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.QueryStatusV2; @@ -49,7 +50,6 @@ import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.client.log.SFLoggerUtil; import net.snowflake.client.util.Stopwatch; -import net.snowflake.common.core.ClientAuthnDTO; import net.snowflake.common.core.SqlState; import org.apache.http.HttpHeaders; import org.apache.http.client.methods.HttpGet; @@ -804,7 +804,7 @@ private boolean isSnowflakeAuthenticator() { && privateKey == null && privateKeyFileLocation == null && privateKeyBase64 == null) - || ClientAuthnDTO.AuthenticatorType.SNOWFLAKE.name().equalsIgnoreCase(authenticator); + || AuthenticatorType.SNOWFLAKE.name().equalsIgnoreCase(authenticator); } /** @@ -815,7 +815,7 @@ private boolean isSnowflakeAuthenticator() { boolean isExternalbrowserAuthenticator() { Map connectionPropertiesMap = getConnectionPropertiesMap(); String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR); - return ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name().equalsIgnoreCase(authenticator); + return AuthenticatorType.EXTERNALBROWSER.name().equalsIgnoreCase(authenticator); } /** @@ -837,9 +837,7 @@ boolean isOKTAAuthenticator() { boolean isUsernamePasswordMFAAuthenticator() { Map connectionPropertiesMap = getConnectionPropertiesMap(); String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR); - return ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA - .name() - .equalsIgnoreCase(authenticator); + return AuthenticatorType.USERNAME_PASSWORD_MFA.name().equalsIgnoreCase(authenticator); } /** diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index de0eb3a87..e13c21162 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -25,6 +25,9 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import net.snowflake.client.core.auth.AuthenticatorType; +import net.snowflake.client.core.auth.ClientAuthnDTO; +import net.snowflake.client.core.auth.ClientAuthnParameter; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.SnowflakeDriver; import net.snowflake.client.jdbc.SnowflakeReauthenticationRequest; @@ -38,8 +41,6 @@ import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.client.util.SecretDetector; import net.snowflake.client.util.Stopwatch; -import net.snowflake.common.core.ClientAuthnDTO; -import net.snowflake.common.core.ClientAuthnParameter; import net.snowflake.common.core.SqlState; import org.apache.http.HttpHeaders; import org.apache.http.client.config.RequestConfig; @@ -209,31 +210,29 @@ public class SessionUtil { * @param loginInput login information * @return Authenticator type */ - private static ClientAuthnDTO.AuthenticatorType getAuthenticator(SFLoginInput loginInput) { + private static AuthenticatorType getAuthenticator(SFLoginInput loginInput) { if (loginInput.getAuthenticator() != null) { if (loginInput .getAuthenticator() - .equalsIgnoreCase(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name())) { + .equalsIgnoreCase(AuthenticatorType.EXTERNALBROWSER.name())) { // SAML 2.0 compliant service/application - return ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER; - } else if (loginInput - .getAuthenticator() - .equalsIgnoreCase(ClientAuthnDTO.AuthenticatorType.OAUTH.name())) { + return AuthenticatorType.EXTERNALBROWSER; + } else if (loginInput.getAuthenticator().equalsIgnoreCase(AuthenticatorType.OAUTH.name())) { // OAuth Authentication - return ClientAuthnDTO.AuthenticatorType.OAUTH; + return AuthenticatorType.OAUTH; } else if (loginInput .getAuthenticator() - .equalsIgnoreCase(ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT.name())) { - return ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT; + .equalsIgnoreCase(AuthenticatorType.SNOWFLAKE_JWT.name())) { + return AuthenticatorType.SNOWFLAKE_JWT; } else if (loginInput .getAuthenticator() - .equalsIgnoreCase(ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA.name())) { - return ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA; + .equalsIgnoreCase(AuthenticatorType.USERNAME_PASSWORD_MFA.name())) { + return AuthenticatorType.USERNAME_PASSWORD_MFA; } else if (!loginInput .getAuthenticator() - .equalsIgnoreCase(ClientAuthnDTO.AuthenticatorType.SNOWFLAKE.name())) { + .equalsIgnoreCase(AuthenticatorType.SNOWFLAKE.name())) { // OKTA authenticator v1. - return ClientAuthnDTO.AuthenticatorType.OKTA; + return AuthenticatorType.OKTA; } } @@ -241,8 +240,8 @@ private static ClientAuthnDTO.AuthenticatorType getAuthenticator(SFLoginInput lo // if privateKey is specified or not. If yes, authenticator type will be // SNOWFLAKE_JWT, otherwise it will use SNOWFLAKE. return loginInput.isPrivateKeyProvided() - ? ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT - : ClientAuthnDTO.AuthenticatorType.SNOWFLAKE; + ? AuthenticatorType.SNOWFLAKE_JWT + : AuthenticatorType.SNOWFLAKE; } /** @@ -266,8 +265,8 @@ static SFLoginOutput openSession( AssertUtil.assertTrue( loginInput.getLoginTimeout() >= 0, "negative login timeout for opening session"); - final ClientAuthnDTO.AuthenticatorType authenticator = getAuthenticator(loginInput); - if (!authenticator.equals(ClientAuthnDTO.AuthenticatorType.OAUTH)) { + final AuthenticatorType authenticator = getAuthenticator(loginInput); + if (!authenticator.equals(AuthenticatorType.OAUTH)) { // OAuth does not require a username AssertUtil.assertTrue( loginInput.getUserName() != null, "missing user name for opening session"); @@ -277,7 +276,7 @@ static SFLoginOutput openSession( loginInput.getToken() != null || loginInput.getPassword() != null, "missing token or password for opening session"); } - if (authenticator.equals(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER)) { + if (authenticator.equals(AuthenticatorType.EXTERNALBROWSER)) { if ((Constants.getOS() == Constants.OS.MAC || Constants.getOS() == Constants.OS.WINDOWS) && loginInput.isEnableClientStoreTemporaryCredential()) { // force to set the flag for Mac/Windows users @@ -299,7 +298,7 @@ static SFLoginOutput openSession( } } - if (authenticator.equals(ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA)) { + if (authenticator.equals(AuthenticatorType.USERNAME_PASSWORD_MFA)) { if ((Constants.getOS() == Constants.OS.MAC || Constants.getOS() == Constants.OS.WINDOWS) && loginInput.isEnableClientRequestMfaToken()) { loginInput.getSessionParameters().put(CLIENT_REQUEST_MFA_TOKEN, true); @@ -371,7 +370,7 @@ private static SFLoginOutput newSession( int healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; int httpClientSocketTimeout = loginInput.getSocketTimeoutInMillis(); int httpClientConnectionTimeout = loginInput.getConnectionTimeoutInMillis(); - final ClientAuthnDTO.AuthenticatorType authenticatorType = getAuthenticator(loginInput); + final AuthenticatorType authenticatorType = getAuthenticator(loginInput); Map commonParams; String oktaUsername = loginInput.getOKTAUserName(); @@ -406,7 +405,7 @@ private static SFLoginOutput newSession( uriBuilder.addParameter(SF_QUERY_ROLE, loginInput.getRole()); } - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER) { + if (authenticatorType == AuthenticatorType.EXTERNALBROWSER) { // try to reuse id_token if exists if (loginInput.getIdToken() == null) { // SAML 2.0 compliant service/application @@ -416,10 +415,10 @@ private static SFLoginOutput newSession( samlProofKey = s.getProofKey(); consentCacheIdToken = s.isConsentCacheIdToken(); } - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + } else if (authenticatorType == AuthenticatorType.OKTA) { // okta authenticator v1 tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput); - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { + } else if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) { SessionUtilKeyPair s = new SessionUtilKeyPair( loginInput.getPrivateKey(), @@ -453,9 +452,6 @@ private static SFLoginOutput newSession( HttpPost postRequest = null; try { - ClientAuthnDTO authnData = new ClientAuthnDTO(); - authnData.setInFlightCtx(loginInput.getInFlightCtx()); - Map data = new HashMap<>(); data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId()); @@ -472,22 +468,21 @@ private static SFLoginOutput newSession( * authenticate with the IDP provider only, and GS should not have any * trace for this information. */ - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE) { + if (authenticatorType == AuthenticatorType.SNOWFLAKE) { data.put(ClientAuthnParameter.PASSWORD.name(), loginInput.getPassword()); - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER) { + } else if (authenticatorType == AuthenticatorType.EXTERNALBROWSER) { if (loginInput.getIdToken() != null) { data.put(ClientAuthnParameter.AUTHENTICATOR.name(), ID_TOKEN_AUTHENTICATOR); data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getIdToken()); } else { data.put( - ClientAuthnParameter.AUTHENTICATOR.name(), - ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()); + ClientAuthnParameter.AUTHENTICATOR.name(), AuthenticatorType.EXTERNALBROWSER.name()); data.put(ClientAuthnParameter.PROOF_KEY.name(), samlProofKey); data.put(ClientAuthnParameter.TOKEN.name(), tokenOrSamlResponse); } - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + } else if (authenticatorType == AuthenticatorType.OKTA) { data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse); - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OAUTH) { + } else if (authenticatorType == AuthenticatorType.OAUTH) { data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name()); // Fix for HikariCP refresh token issue:SNOW-533673. @@ -499,10 +494,10 @@ private static SFLoginOutput newSession( data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getPassword()); } - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { + } else if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) { data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name()); data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getToken()); - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA) { + } else if (authenticatorType == AuthenticatorType.USERNAME_PASSWORD_MFA) { // No authenticator name should be added here, since this will be treated as snowflake // default authenticator by backend data.put(ClientAuthnParameter.PASSWORD.name(), loginInput.getPassword()); @@ -621,8 +616,7 @@ private static SFLoginOutput newSession( } data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion()); - - authnData.setData(data); + ClientAuthnDTO authnData = new ClientAuthnDTO(data, loginInput.getInFlightCtx()); String json = mapper.writeValueAsString(authnData); postRequest = new HttpPost(loginURI); @@ -672,10 +666,10 @@ private static SFLoginOutput newSession( } catch (SnowflakeSQLException ex) { lastRestException = ex; if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) { - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT - || authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT + || authenticatorType == AuthenticatorType.OKTA) { - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT) { + if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) { SessionUtilKeyPair s = new SessionUtilKeyPair( loginInput.getPrivateKey(), @@ -686,13 +680,14 @@ private static SFLoginOutput newSession( loginInput.getUserName()); data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken()); - } else if (authenticatorType == ClientAuthnDTO.AuthenticatorType.OKTA) { + } else if (authenticatorType == 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); - authnData.setData(data); - String updatedJson = mapper.writeValueAsString(authnData); + ClientAuthnDTO updatedAuthnData = + new ClientAuthnDTO(data, loginInput.getInFlightCtx()); + String updatedJson = mapper.writeValueAsString(updatedAuthnData); StringEntity updatedInput = new StringEntity(updatedJson, StandardCharsets.UTF_8); updatedInput.setContentType("application/json"); @@ -785,7 +780,7 @@ private static SFLoginOutput newSession( SnowflakeUtil.checkErrorAndThrowExceptionIncludingReauth(jsonNode); } - if (authenticatorType == ClientAuthnDTO.AuthenticatorType.USERNAME_PASSWORD_MFA) { + if (authenticatorType == AuthenticatorType.USERNAME_PASSWORD_MFA) { deleteMfaTokenCache(loginInput.getHostFromServerUrl(), loginInput.getUserName()); } @@ -1378,8 +1373,7 @@ private static JsonNode federatedFlowStep1(SFLoginInput loginInput) throws Snowf data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId()); data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion()); - ClientAuthnDTO authnData = new ClientAuthnDTO(); - authnData.setData(data); + ClientAuthnDTO authnData = new ClientAuthnDTO(data, null); String json = mapper.writeValueAsString(authnData); // attach the login info json body to the post request diff --git a/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java b/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java index 8d6008e38..0f83a9642 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java +++ b/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java @@ -28,12 +28,12 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import net.snowflake.client.core.auth.ClientAuthnDTO; +import net.snowflake.client.core.auth.ClientAuthnParameter; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.SnowflakeSQLException; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.common.core.ClientAuthnDTO; -import net.snowflake.common.core.ClientAuthnParameter; import net.snowflake.common.core.SqlState; import org.apache.http.NameValuePair; import org.apache.http.client.methods.HttpPost; @@ -175,7 +175,6 @@ private String getSSOUrl(int port) throws SFException, SnowflakeSQLException { HttpPost postRequest = this.handlers.build(fedUrlUri); - ClientAuthnDTO authnData = new ClientAuthnDTO(); Map data = new HashMap<>(); data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticator); @@ -185,7 +184,7 @@ private String getSSOUrl(int port) throws SFException, SnowflakeSQLException { data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId()); data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion()); - authnData.setData(data); + ClientAuthnDTO authnData = new ClientAuthnDTO(data, null); String json = mapper.writeValueAsString(authnData); // attach the login info json body to the post request diff --git a/src/main/java/net/snowflake/client/core/SfSqlArray.java b/src/main/java/net/snowflake/client/core/SfSqlArray.java index 70682b4f4..4966d7ab8 100644 --- a/src/main/java/net/snowflake/client/core/SfSqlArray.java +++ b/src/main/java/net/snowflake/client/core/SfSqlArray.java @@ -1,6 +1,7 @@ package net.snowflake.client.core; import static net.snowflake.client.core.FieldSchemaCreator.buildBindingSchemaForType; +import static net.snowflake.client.core.FieldSchemaCreator.logger; import com.fasterxml.jackson.core.JsonProcessingException; import java.sql.Array; @@ -16,14 +17,21 @@ @SnowflakeJdbcInternalApi public class SfSqlArray implements Array { + private String text; private int baseType; private Object elements; + private String jsonStringFromElements; - public SfSqlArray(int baseType, Object elements) { + public SfSqlArray(String text, int baseType, Object elements) { + this.text = text; this.baseType = baseType; this.elements = elements; } + public SfSqlArray(int baseType, Object elements) { + this(null, baseType, elements); + } + @Override public String getBaseTypeName() throws SQLException { return JDBCType.valueOf(baseType).getName(); @@ -81,7 +89,22 @@ public ResultSet getResultSet(long index, int count, Map> map) @Override public void free() throws SQLException {} + public String getText() { + if (text == null) { + logger.warn("Text field wasn't initialized. Should never happen."); + } + return text; + } + public String getJsonString() throws SQLException { + if (jsonStringFromElements == null) { + jsonStringFromElements = buildJsonStringFromElements(elements); + } + + return jsonStringFromElements; + } + + private static String buildJsonStringFromElements(Object elements) throws SQLException { try { return SnowflakeUtil.mapJson(elements); } catch (JsonProcessingException e) { diff --git a/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java b/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java index ad9926eac..96b683151 100644 --- a/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java @@ -25,7 +25,12 @@ public ArrayConverter(ListVector valueVector, int vectorIndex, DataConversionCon @Override public Object toObject(int index) throws SFException { - return vector.getObject(index); + return isNull(index) ? null : new StructObjectWrapper(toString(index), vector.getObject(index)); + } + + @Override + public byte[] toBytes(int index) throws SFException { + return isNull(index) ? null : toString(index).getBytes(); } @Override diff --git a/src/main/java/net/snowflake/client/core/arrow/MapConverter.java b/src/main/java/net/snowflake/client/core/arrow/MapConverter.java index 4099cd5fb..0c6ca072e 100644 --- a/src/main/java/net/snowflake/client/core/arrow/MapConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/MapConverter.java @@ -1,6 +1,8 @@ package net.snowflake.client.core.arrow; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import net.snowflake.client.core.DataConversionContext; import net.snowflake.client.core.SFException; @@ -28,11 +30,23 @@ public MapConverter(MapVector valueVector, int columnIndex, DataConversionContex @Override public Object toObject(int index) throws SFException { + if (isNull(index)) { + return null; + } + List> entriesList = (List>) vector.getObject(index); - return entriesList.stream() - .collect( - Collectors.toMap(entry -> entry.get("key").toString(), entry -> entry.get("value"))); + Map map = + entriesList.stream() + .collect( + Collectors.toMap( + entry -> entry.get("key").toString(), entry -> entry.get("value"))); + return new StructObjectWrapper(toString(index), map); + } + + @Override + public byte[] toBytes(int index) throws SFException { + return isNull(index) ? null : toString(index).getBytes(StandardCharsets.UTF_8); } @Override diff --git a/src/main/java/net/snowflake/client/core/arrow/StructConverter.java b/src/main/java/net/snowflake/client/core/arrow/StructConverter.java index 4c0516c51..ab7d20382 100644 --- a/src/main/java/net/snowflake/client/core/arrow/StructConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/StructConverter.java @@ -21,7 +21,14 @@ public StructConverter(StructVector vector, int columnIndex, DataConversionConte @Override public Object toObject(int index) throws SFException { - return structVector.getObject(index); + return isNull(index) + ? null + : new StructObjectWrapper(toString(index), structVector.getObject(index)); + } + + @Override + public byte[] toBytes(int index) throws SFException { + return isNull(index) ? null : toString(index).getBytes(); } @Override @@ -32,9 +39,13 @@ public String toString(int index) throws SFException { SnowflakeType logicalType = ArrowVectorConverterUtil.getSnowflakeTypeFromFieldMetadata(fieldVector.getField()); try { - ArrowVectorConverter converter = - ArrowVectorConverterUtil.initConverter(fieldVector, context, columnIndex); - builder.appendKeyValue(childName, converter.toString(index), logicalType); + if (fieldVector.isNull(index)) { + builder.appendKeyValue(childName, null, logicalType); + } else { + ArrowVectorConverter converter = + ArrowVectorConverterUtil.initConverter(fieldVector, context, columnIndex); + builder.appendKeyValue(childName, converter.toString(index), logicalType); + } } catch (SnowflakeSQLException e) { return structVector.getObject(index).toString(); } diff --git a/src/main/java/net/snowflake/client/core/arrow/StructObjectWrapper.java b/src/main/java/net/snowflake/client/core/arrow/StructObjectWrapper.java new file mode 100644 index 000000000..8219c110a --- /dev/null +++ b/src/main/java/net/snowflake/client/core/arrow/StructObjectWrapper.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. + */ +package net.snowflake.client.core.arrow; + +import net.snowflake.client.core.SnowflakeJdbcInternalApi; + +@SnowflakeJdbcInternalApi +public class StructObjectWrapper { + private final String jsonString; + private final Object object; + + public StructObjectWrapper(String jsonString, Object object) { + this.jsonString = jsonString; + this.object = object; + } + + public String getJsonString() { + return jsonString; + } + + public Object getObject() { + return object; + } +} diff --git a/src/main/java/net/snowflake/client/core/arrow/VectorTypeConverter.java b/src/main/java/net/snowflake/client/core/arrow/VectorTypeConverter.java index 8d1ae2942..cb9dcad73 100644 --- a/src/main/java/net/snowflake/client/core/arrow/VectorTypeConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/VectorTypeConverter.java @@ -24,7 +24,16 @@ public VectorTypeConverter( @Override public Object toObject(int index) throws SFException { - return vector.getObject(index); + if (isNull(index)) { + return null; + } + Object object = vector.getObject(index); + return new StructObjectWrapper(object.toString(), object); + } + + @Override + public byte[] toBytes(int index) throws SFException { + return isNull(index) ? null : toString(index).getBytes(); } @Override diff --git a/src/main/java/net/snowflake/client/core/arrow/tostringhelpers/ArrowStringRepresentationBuilderBase.java b/src/main/java/net/snowflake/client/core/arrow/tostringhelpers/ArrowStringRepresentationBuilderBase.java index cc25bb7e0..f60daa0cc 100644 --- a/src/main/java/net/snowflake/client/core/arrow/tostringhelpers/ArrowStringRepresentationBuilderBase.java +++ b/src/main/java/net/snowflake/client/core/arrow/tostringhelpers/ArrowStringRepresentationBuilderBase.java @@ -21,7 +21,6 @@ public abstract class ArrowStringRepresentationBuilderBase { quotableTypes.add(SnowflakeType.ANY); quotableTypes.add(SnowflakeType.CHAR); quotableTypes.add(SnowflakeType.TEXT); - quotableTypes.add(SnowflakeType.VARIANT); quotableTypes.add(SnowflakeType.BINARY); quotableTypes.add(SnowflakeType.DATE); quotableTypes.add(SnowflakeType.TIME); @@ -44,6 +43,9 @@ private boolean shouldQuoteValue(SnowflakeType type) { } protected String quoteIfNeeded(String string, SnowflakeType type) { + if (string == null) { + return null; + } // Turn Boolean string representations lowercase to make the output JSON-compatible // this should be changed on the converter level, but it would be a breaking change thus // for now only structured types will be valid JSONs while in NATIVE ARROW mode diff --git a/src/main/java/net/snowflake/client/core/auth/AuthenticatorType.java b/src/main/java/net/snowflake/client/core/auth/AuthenticatorType.java new file mode 100644 index 000000000..e25af718a --- /dev/null +++ b/src/main/java/net/snowflake/client/core/auth/AuthenticatorType.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All right reserved. + */ +package net.snowflake.client.core.auth; + +import net.snowflake.client.core.SnowflakeJdbcInternalApi; + +@SnowflakeJdbcInternalApi +public enum AuthenticatorType { + /* + * regular login username+password via Snowflake, may or may not have MFA + */ + SNOWFLAKE, + + /* + * federated authentication, OKTA as IDP + */ + OKTA, + + /* + * Web browser based authenticator for SAML 2.0 compliant + * service/application + */ + EXTERNALBROWSER, + + /* + * OAUTH 2.0 flow + */ + OAUTH, + + /* + * Snowflake local authentication using jwt token as a user credential + */ + SNOWFLAKE_JWT, + + /* + * Internal authenticator to enable id_token for web browser based authenticator + */ + ID_TOKEN, + + /* + * Authenticator to enable token for regular login with mfa + */ + USERNAME_PASSWORD_MFA +} diff --git a/src/main/java/net/snowflake/client/core/auth/ClientAuthnDTO.java b/src/main/java/net/snowflake/client/core/auth/ClientAuthnDTO.java new file mode 100644 index 000000000..98626d86a --- /dev/null +++ b/src/main/java/net/snowflake/client/core/auth/ClientAuthnDTO.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All right reserved. + */ +package net.snowflake.client.core.auth; + +import java.util.Map; +import javax.annotation.Nullable; +import net.snowflake.client.core.SnowflakeJdbcInternalApi; + +@SnowflakeJdbcInternalApi +public class ClientAuthnDTO { + + // contains all the required data for current authn step + private final Map data; + + /* + * current state + * tokenized string with all current parameters and the authn step + */ + private final String inFlightCtx; + + public ClientAuthnDTO(Map data, @Nullable String inFlightCtx) { + this.data = data; + this.inFlightCtx = inFlightCtx; + } + + /** Required by Jackson */ + public Map getData() { + return data; + } + + /** Required by Jackson */ + public String getInFlightCtx() { + return inFlightCtx; + } +} diff --git a/src/main/java/net/snowflake/client/core/auth/ClientAuthnParameter.java b/src/main/java/net/snowflake/client/core/auth/ClientAuthnParameter.java new file mode 100644 index 000000000..8571a013b --- /dev/null +++ b/src/main/java/net/snowflake/client/core/auth/ClientAuthnParameter.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All right reserved. + */ +package net.snowflake.client.core.auth; + +import net.snowflake.client.core.SnowflakeJdbcInternalApi; + +@SnowflakeJdbcInternalApi +public enum ClientAuthnParameter { + LOGIN_NAME, + PASSWORD, + RAW_SAML_RESPONSE, + ACCOUNT_NAME, + CLIENT_APP_ID, + CLIENT_APP_VERSION, + EXT_AUTHN_DUO_METHOD, + PASSCODE, + CLIENT_ENVIRONMENT, + AUTHENTICATOR, + BROWSER_MODE_REDIRECT_PORT, + SESSION_PARAMETERS, + PROOF_KEY, + TOKEN +} diff --git a/src/main/java/net/snowflake/client/core/json/BytesConverter.java b/src/main/java/net/snowflake/client/core/json/BytesConverter.java index 8212e5830..eb1f80a5a 100644 --- a/src/main/java/net/snowflake/client/core/json/BytesConverter.java +++ b/src/main/java/net/snowflake/client/core/json/BytesConverter.java @@ -1,6 +1,7 @@ package net.snowflake.client.core.json; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.sql.Types; import net.snowflake.client.core.SFException; import net.snowflake.client.jdbc.ErrorCode; @@ -46,10 +47,13 @@ public byte[] getBytes(Object obj, int columnType, int columnSubType, Integer sc .toByteArray(); case Types.VARCHAR: case Types.CHAR: + case Types.STRUCT: + case Types.ARRAY: + case SnowflakeUtil.EXTRA_TYPES_VECTOR: return converters .getStringConverter() .getString(obj, columnType, columnSubType, scale) - .getBytes(); + .getBytes(StandardCharsets.UTF_8); case Types.BOOLEAN: return converters.getBooleanConverter().getBoolean(obj, columnType) ? new byte[] {1} diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index ced00e325..633083391 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -45,6 +45,7 @@ import net.snowflake.client.core.SFBaseResultSet; import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.SFException; +import net.snowflake.client.core.arrow.StructObjectWrapper; import net.snowflake.client.core.structs.SQLDataCreationHelper; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; @@ -1395,7 +1396,11 @@ public T getObject(int columnIndex, Class type) throws SQLException { if (SQLData.class.isAssignableFrom(type)) { SQLInput sqlInput = SnowflakeUtil.mapSFExceptionToSQLException( - () -> (SQLInput) sfBaseResultSet.getObject(columnIndex)); + () -> { + StructObjectWrapper structObjectWrapper = + (StructObjectWrapper) sfBaseResultSet.getObject(columnIndex); + return (SQLInput) createJsonSqlInput(columnIndex, structObjectWrapper); + }); if (sqlInput == null) { return null; } else { @@ -1631,13 +1636,16 @@ public Map getMap(int columnIndex, Class type) throws SQLExcep int columnType = ColumnTypeHelper.getColumnType(valueFieldMetadata.getType(), session); int scale = valueFieldMetadata.getScale(); TimeZone tz = sfBaseResultSet.getSessionTimeZone(); - Object object = - SnowflakeUtil.mapSFExceptionToSQLException(() -> sfBaseResultSet.getObject(columnIndex)); - if (object == null) { + StructObjectWrapper structObjectWrapper = + (StructObjectWrapper) + SnowflakeUtil.mapSFExceptionToSQLException( + () -> sfBaseResultSet.getObject(columnIndex)); + if (structObjectWrapper == null) { return null; } Map map = - mapSFExceptionToSQLException(() -> prepareMapWithValues(object, type)); + mapSFExceptionToSQLException( + () -> prepareMapWithValues(structObjectWrapper.getObject(), type)); Map resultMap = new HashMap<>(); for (Map.Entry entry : map.entrySet()) { if (SQLData.class.isAssignableFrom(type)) { @@ -1645,7 +1653,7 @@ public Map getMap(int columnIndex, Class type) throws SQLExcep SQLInput sqlInput = sfBaseResultSet.createSqlInputForColumn( entry.getValue(), - object.getClass(), + structObjectWrapper.getObject().getClass(), columnIndex, session, valueFieldMetadata.getFields()); @@ -1824,4 +1832,22 @@ private Map prepareMapWithValues(Object object, Class typ throw new SFException(ErrorCode.INVALID_STRUCT_DATA, "Object couldn't be converted to map"); } } + + private Object createJsonSqlInput(int columnIndex, StructObjectWrapper obj) throws SFException { + try { + if (obj == null) { + return null; + } + JsonNode jsonNode = OBJECT_MAPPER.readTree(obj.getJsonString()); + return new JsonSqlInput( + obj.getJsonString(), + jsonNode, + session, + sfBaseResultSet.getConverters(), + sfBaseResultSet.getMetaData().getColumnFields(columnIndex), + sfBaseResultSet.getSessionTimeZone()); + } catch (JsonProcessingException e) { + throw new SFException(sfBaseResultSet.getQueryId(), e, ErrorCode.INVALID_STRUCT_DATA); + } + } } diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java index 4f73b4c18..e9db5ec71 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetV1.java @@ -33,6 +33,8 @@ import net.snowflake.client.core.QueryStatus; import net.snowflake.client.core.SFBaseResultSet; import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SfSqlArray; +import net.snowflake.client.core.arrow.StructObjectWrapper; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; @@ -274,6 +276,10 @@ public Object getObject(int columnIndex) throws SQLException { return null; } else if (object instanceof JsonSqlInput) { return ((JsonSqlInput) object).getText(); + } else if (object instanceof StructObjectWrapper) { + return ((StructObjectWrapper) object).getJsonString(); + } else if (object instanceof SfSqlArray) { + return ((SfSqlArray) object).getText(); } else if (object instanceof ArrowSqlInput) { throw new SQLException( "Arrow native struct couldn't be converted to String. To map to SqlData the method getObject(int columnIndex, Class type) should be used"); diff --git a/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java b/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java index 02f6193d6..0470ef6e8 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java @@ -28,7 +28,6 @@ import net.snowflake.client.jdbc.SnowflakeBasicDataSource; import net.snowflake.client.jdbc.SnowflakeSQLException; import net.snowflake.client.jdbc.SnowflakeSQLLoggedException; -import net.snowflake.common.core.ClientAuthnDTO; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.hamcrest.MatcherAssert; @@ -239,8 +238,7 @@ private SFLoginInput initMockLoginInput() { // mock SFLoginInput SFLoginInput loginInput = mock(SFLoginInput.class); when(loginInput.getServerUrl()).thenReturn("https://testaccount.snowflakecomputing.com/"); - when(loginInput.getAuthenticator()) - .thenReturn(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()); + when(loginInput.getAuthenticator()).thenReturn("EXTERNALBROWSER"); when(loginInput.getAccountName()).thenReturn("testaccount"); when(loginInput.getUserName()).thenReturn("testuser"); when(loginInput.getDisableConsoleLogin()).thenReturn(true); diff --git a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java index 57dde2a7b..434dd8eac 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilLatestIT.java @@ -22,10 +22,10 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import net.snowflake.client.category.TestTags; +import net.snowflake.client.core.auth.AuthenticatorType; import net.snowflake.client.jdbc.BaseJDBCTest; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.SnowflakeSQLException; -import net.snowflake.common.core.ClientAuthnDTO; import net.snowflake.common.core.SqlState; import org.apache.commons.io.IOUtils; import org.apache.http.Header; @@ -85,8 +85,7 @@ private SFLoginInput initMockLoginInput() { // mock SFLoginInput SFLoginInput loginInput = mock(SFLoginInput.class); when(loginInput.getServerUrl()).thenReturn(systemGetEnv("SNOWFLAKE_TEST_HOST")); - when(loginInput.getAuthenticator()) - .thenReturn(ClientAuthnDTO.AuthenticatorType.SNOWFLAKE_JWT.name()); + when(loginInput.getAuthenticator()).thenReturn(AuthenticatorType.SNOWFLAKE_JWT.name()); when(loginInput.getPrivateKeyFile()) .thenReturn(systemGetEnv("SNOWFLAKE_TEST_PRIVATE_KEY_FILE")); when(loginInput.getPrivateKeyPwd()).thenReturn(systemGetEnv("SNOWFLAKE_TEST_PRIVATE_KEY_PWD")); diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java index 68cd101bf..b58643460 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionLatestIT.java @@ -63,11 +63,12 @@ import net.snowflake.client.core.SFSessionProperty; import net.snowflake.client.core.SecurityUtil; import net.snowflake.client.core.SessionUtil; +import net.snowflake.client.core.auth.AuthenticatorType; +import net.snowflake.client.core.auth.ClientAuthnDTO; +import net.snowflake.client.core.auth.ClientAuthnParameter; import net.snowflake.client.jdbc.telemetryOOB.TelemetryService; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.common.core.ClientAuthnDTO; -import net.snowflake.common.core.ClientAuthnParameter; import net.snowflake.common.core.SqlState; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; @@ -570,7 +571,7 @@ public void testHttpsLoginTimeoutWithOutSSL() throws InterruptedException { properties.put("user", "fakeuser"); properties.put("password", "fakepassword"); // Adding authenticator type for code coverage purposes - properties.put("authenticator", ClientAuthnDTO.AuthenticatorType.SNOWFLAKE.toString()); + properties.put("authenticator", AuthenticatorType.SNOWFLAKE.toString()); properties.put("ssl", "off"); int count = TelemetryService.getInstance().getEventCount(); try { @@ -633,7 +634,7 @@ public void testWrongHostNameTimeout() throws InterruptedException { properties.put("user", "fakeuser"); properties.put("password", "fakepassword"); // Adding authenticator type for code coverage purposes - properties.put("authenticator", ClientAuthnDTO.AuthenticatorType.SNOWFLAKE.toString()); + properties.put("authenticator", AuthenticatorType.SNOWFLAKE.toString()); try { connStart = System.currentTimeMillis(); Map params = getConnectionParameters(); @@ -1252,8 +1253,7 @@ public void testAuthenticatorEndpointWithDashInAccountName() throws Exception { Map data = Collections.singletonMap(ClientAuthnParameter.ACCOUNT_NAME.name(), "snowhouse-local"); - ClientAuthnDTO authnData = new ClientAuthnDTO(); - authnData.setData(data); + ClientAuthnDTO authnData = new ClientAuthnDTO(data, null); ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(); String json = mapper.writeValueAsString(authnData); diff --git a/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java index 6fbadd92f..71618c9e9 100644 --- a/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java @@ -35,7 +35,6 @@ import net.snowflake.client.core.SFLoginInput; import net.snowflake.client.core.SessionUtil; import net.snowflake.client.core.SessionUtilExternalBrowser; -import net.snowflake.common.core.ClientAuthnDTO; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.HttpPost; import org.junit.jupiter.api.Test; @@ -259,7 +258,7 @@ public String answer(InvocationOnMock invocation) throws IOException { assertThat( "authenticator", jsonNode.path("data").path("AUTHENTICATOR").asText(), - equalTo(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name())); + equalTo("EXTERNALBROWSER")); resp = retInitialAuthentication; } else if (callCount == 2) { jsonNode = parseRequest((HttpPost) args[0]); @@ -298,8 +297,7 @@ private void initMockSessionUtilExternalBrowser( private SFLoginInput initMockLoginInput() { SFLoginInput loginInput = mock(SFLoginInput.class); when(loginInput.getServerUrl()).thenReturn("https://testaccount.snowflakecomputing.com/"); - when(loginInput.getAuthenticator()) - .thenReturn(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()); + when(loginInput.getAuthenticator()).thenReturn("EXTERNALBROWSER"); when(loginInput.getAccountName()).thenReturn("testaccount"); when(loginInput.getUserName()).thenReturn("testuser"); when(loginInput.getDisableConsoleLogin()).thenReturn(true); diff --git a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverIT.java b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverIT.java index b245f8c0b..e23dfbfae 100644 --- a/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverIT.java +++ b/src/test/java/net/snowflake/client/jdbc/SnowflakeDriverIT.java @@ -54,7 +54,6 @@ import net.snowflake.client.annotations.DontRunOnGithubActions; import net.snowflake.client.annotations.DontRunOnTestaccount; import net.snowflake.client.category.TestTags; -import net.snowflake.common.core.ClientAuthnDTO; import net.snowflake.common.core.SqlState; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterAll; @@ -173,7 +172,7 @@ public void testOauthConnection() throws SQLException { } } Properties props = new Properties(); - props.put("authenticator", ClientAuthnDTO.AuthenticatorType.OAUTH.name()); + props.put("authenticator", "OAUTH"); props.put("token", token); props.put("role", role); try (Connection con = getConnection("s3testaccount", props); diff --git a/src/test/java/net/snowflake/client/jdbc/structuredtypes/ResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/structuredtypes/ResultSetStructuredTypesLatestIT.java index 1660ea57d..f9b3368d8 100644 --- a/src/test/java/net/snowflake/client/jdbc/structuredtypes/ResultSetStructuredTypesLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/structuredtypes/ResultSetStructuredTypesLatestIT.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; @@ -39,6 +38,7 @@ import net.snowflake.client.jdbc.structuredtypes.sqldata.SimpleClass; import net.snowflake.client.jdbc.structuredtypes.sqldata.StringClass; import net.snowflake.client.providers.ResultFormatProvider; +import org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; @@ -201,7 +201,7 @@ public void testReturnStructAsStringIfTypeWasNotIndicated(ResultSetFormatType fo try (ResultSet resultSet = statement.executeQuery(AllTypesClass.ALL_TYPES_QUERY); ) { resultSet.next(); String object = (String) resultSet.getObject(1); - String expected = + String expectedJson = "{\n" + " \"string\": \"a\",\n" + " \"b\": 1,\n" @@ -223,31 +223,20 @@ public void testReturnStructAsStringIfTypeWasNotIndicated(ResultSetFormatType fo + " \"intValue\": 2\n" + " }\n" + "}"; - assertEquals(expected, object); + String expectedJsonFromArrow = + "{\"string\": \"a\",\"b\": 1,\"s\": 2,\"i\": 3,\"l\": 4,\"f\": 1.1,\"d\": 2.2,\"bd\": 3.3," + + "\"bool\": true,\"timestamp_ltz\": \"2021-12-22 09:43:44.000 +0100\",\"timestamp_ntz\": \"2021-12-23 09:44:44.000\"," + + "\"timestamp_tz\": \"2021-12-24 09:45:45.000 +0800\",\"date\": \"2023-12-24\",\"time\": \"12:34:56\",\"binary\": \"616263\"," + + "\"simpleClass\": {\"string\": \"b\",\"intValue\": 2}}"; + if (format == ResultSetFormatType.NATIVE_ARROW) { + Assert.assertEquals(expectedJsonFromArrow, object); + } else { + Assert.assertEquals(expectedJson, object); + } } } } - @ParameterizedTest - @ArgumentsSource(ResultFormatProvider.class) - @DontRunOnGithubActions - public void testThrowingGettingObjectIfTypeWasNotIndicatedAndFormatNativeArrow( - ResultSetFormatType format) throws SQLException { - Assumptions.assumeTrue(format == ResultSetFormatType.NATIVE_ARROW); - withFirstRow( - "select {'string':'a'}::OBJECT(string VARCHAR)", - (resultSet) -> { - assertThrows(SQLException.class, () -> resultSet.getObject(1)); - }, - format); - withFirstRow( - "select {'x':{'string':'one'},'y':{'string':'two'},'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));", - (resultSet) -> { - assertThrows(SQLException.class, () -> resultSet.getObject(1, Map.class)); - }, - format); - } - @ParameterizedTest @ArgumentsSource(ResultFormatProvider.class) @DontRunOnGithubActions diff --git a/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringArrowJsonCompatibilityIT.java b/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesArrowJsonCompatibilityLatestIT.java similarity index 95% rename from src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringArrowJsonCompatibilityIT.java rename to src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesArrowJsonCompatibilityLatestIT.java index c3ae5fdd8..77bfca52d 100644 --- a/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringArrowJsonCompatibilityIT.java +++ b/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesArrowJsonCompatibilityLatestIT.java @@ -21,8 +21,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; @Tag(TestTags.RESULT_SET) -public class StructuredTypesGetStringArrowJsonCompatibilityIT - extends StructuredTypesGetStringBaseIT { +public class StructuredTypesArrowJsonCompatibilityLatestIT extends StructuredTypesGetStringBaseIT { private static Map connections = new HashMap<>(); @BeforeAll @@ -44,7 +43,7 @@ public static void closeConnections() throws SQLException { @ParameterizedTest @DontRunOnGithubActions @ArgumentsSource(DataProvider.class) - public void testRunAsGetString( + public void testArrowJsonCompatibility( ResultSetFormatType queryResultFormat, String selectSql, String expectedStructureTypeRepresentation) @@ -52,7 +51,7 @@ public void testRunAsGetString( withFirstRow( connections.get(queryResultFormat), selectSql, - (resultSet) -> assertGetStringIsCompatible(resultSet, expectedStructureTypeRepresentation)); + (resultSet) -> assertResultSetIsCompatible(resultSet, expectedStructureTypeRepresentation)); } public static class SampleProvider extends SnowflakeArgumentsProvider { @@ -146,6 +145,7 @@ protected List rawArguments(ExtensionContext context) { "{\"binary\":\"616263\"}")); samples.add(Arguments.of("select [1,2,3]::VECTOR(INT, 3)", "[1,2,3]")); samples.add(Arguments.of("select ['a','b','c']::ARRAY(varchar)", "[\"a\",\"b\",\"c\"]")); + samples.add(Arguments.of("select ['a','b','c']::ARRAY(variant)", "[\"a\",\"b\",\"c\"]")); return samples; } diff --git a/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringBaseIT.java b/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringBaseIT.java index 35d10c4b1..767e64e18 100644 --- a/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringBaseIT.java +++ b/src/test/java/net/snowflake/client/jdbc/structuredtypes/StructuredTypesGetStringBaseIT.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -45,10 +46,21 @@ protected static Connection initConnection(ResultSetFormatType queryResultFormat return conn; } - protected void assertGetStringIsCompatible(ResultSet resultSet, String expected) + protected void assertResultSetIsCompatible(ResultSet resultSet, String expected) throws SQLException { + // Test getString String result = resultSet.getString(1); TestUtil.assertEqualsIgnoringWhitespace(expected, result); + + // Test getObject + result = resultSet.getObject(1, String.class); + String resultCasted = (String) resultSet.getObject(1); + TestUtil.assertEqualsIgnoringWhitespace(expected, result); + TestUtil.assertEqualsIgnoringWhitespace(expected, resultCasted); + + // Test getBytes + TestUtil.assertEqualsIgnoringWhitespace( + expected, new String(resultSet.getBytes(1), StandardCharsets.UTF_8)); } protected void withFirstRow(