From a6f52619797f7d332d39ede8c06dd63c17c05b96 Mon Sep 17 00:00:00 2001 From: Piotr Bulawa Date: Fri, 12 Jan 2024 17:26:43 +0100 Subject: [PATCH] SNOW-993556: Replace underscores with hyphen (#848) Co-authored-by: Brad Culberson --- README.md | 61 ++++++------- .../IntegrationTests/SFConnectionIT.cs | 6 +- .../UnitTests/SFSessionPropertyTest.cs | 85 +++++++++++++++++-- .../Core/Session/SFSessionProperty.cs | 31 ++++++- 4 files changed, 139 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 9168036f5..48c48d19b 100644 --- a/README.md +++ b/README.md @@ -133,37 +133,38 @@ i.e "\=\;\=\...". The following table lists all valid connection properties:
-| Connection Property | Required | Comment | -|--------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ACCOUNT | Yes | Your full account name might include additional segments that identify the region and cloud platform where your account is hosted | -| APPLICATION | No | **_Snowflake partner use only_**: Specifies the name of a partner application to connect through .NET. The name must match the following pattern: ^\[A-Za-z](\[A-Za-z0-9.-]){1,50}$ (one letter followed by 1 to 50 letter, digit, .,- or, \_ characters). | -| DB | No | | -| HOST | No | Specifies the hostname for your account in the following format: \.snowflakecomputing.com.
If no value is specified, the driver uses \.snowflakecomputing.com. | -| PASSWORD | Depends | Required if AUTHENTICATOR is set to `snowflake` (the default value) or the URL for native SSO through Okta. Ignored for all the other authentication types. | -| ROLE | No | | -| SCHEMA | No | | -| USER | Depends | If AUTHENTICATOR is set to `externalbrowser` this is optional. For native SSO through Okta, set this to the login name for your identity provider (IdP). | -| WAREHOUSE | No | | -| CONNECTION_TIMEOUT | No | Total timeout in seconds when connecting to Snowflake. The default is 300 seconds | -| RETRY_TIMEOUT | No | Total timeout in seconds for supported endpoints of retry policy. The default is 300 seconds. The value can only be increased from the default value or set to 0 for infinite timeout | -| MAXHTTPRETRIES | No | Maximum number of times to retry failed HTTP requests (default: 7). You can set `MAXHTTPRETRIES=0` to remove the retry limit, but doing so runs the risk of the .NET driver infinitely retrying failed HTTP calls. | -| CLIENT_SESSION_KEEP_ALIVE | No | Whether to keep the current session active after a period of inactivity, or to force the user to login again. If the value is `true`, Snowflake keeps the session active indefinitely, even if there is no activity from the user. If the value is `false`, the user must log in again after four hours of inactivity. The default is `false`. Setting this value overrides the server session property for the current session. | -| DISABLERETRY | No | Set this property to `true` to prevent the driver from reconnecting automatically when the connection fails or drops. The default value is `false`. | +| Connection Property | Required | Comment | +|--------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ACCOUNT | Yes | Your full account name might include additional segments that identify the region and cloud platform where your account is hosted | +| APPLICATION | No | **_Snowflake partner use only_**: Specifies the name of a partner application to connect through .NET. The name must match the following pattern: ^\[A-Za-z](\[A-Za-z0-9.-]){1,50}$ (one letter followed by 1 to 50 letter, digit, .,- or, \_ characters). | +| DB | No | | +| HOST | No | Specifies the hostname for your account in the following format: \.snowflakecomputing.com.
If no value is specified, the driver uses \.snowflakecomputing.com. | +| PASSWORD | Depends | Required if AUTHENTICATOR is set to `snowflake` (the default value) or the URL for native SSO through Okta. Ignored for all the other authentication types. | +| ROLE | No | | +| SCHEMA | No | | +| USER | Depends | If AUTHENTICATOR is set to `externalbrowser` this is optional. For native SSO through Okta, set this to the login name for your identity provider (IdP). | +| WAREHOUSE | No | | +| CONNECTION_TIMEOUT | No | Total timeout in seconds when connecting to Snowflake. The default is 300 seconds | +| RETRY_TIMEOUT | No | Total timeout in seconds for supported endpoints of retry policy. The default is 300 seconds. The value can only be increased from the default value or set to 0 for infinite timeout | +| MAXHTTPRETRIES | No | Maximum number of times to retry failed HTTP requests (default: 7). You can set `MAXHTTPRETRIES=0` to remove the retry limit, but doing so runs the risk of the .NET driver infinitely retrying failed HTTP calls. | +| CLIENT_SESSION_KEEP_ALIVE | No | Whether to keep the current session active after a period of inactivity, or to force the user to login again. If the value is `true`, Snowflake keeps the session active indefinitely, even if there is no activity from the user. If the value is `false`, the user must log in again after four hours of inactivity. The default is `false`. Setting this value overrides the server session property for the current session. | +| DISABLERETRY | No | Set this property to `true` to prevent the driver from reconnecting automatically when the connection fails or drops. The default value is `false`. | | AUTHENTICATOR | No | The method of authentication. Currently supports the following values:
- snowflake (default): You must also set USER and PASSWORD.
- [the URL for native SSO through Okta](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#native-sso-okta-only): You must also set USER and PASSWORD.
- [externalbrowser](https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use.html#browser-based-sso): You must also set USER.
- [snowflake_jwt](https://docs.snowflake.com/en/user-guide/key-pair-auth.html): You must also set PRIVATE_KEY_FILE or PRIVATE_KEY.
- [oauth](https://docs.snowflake.com/en/user-guide/oauth.html): You must also set TOKEN. | BROWSER_RESPONSE_TIMEOUT | No | Number to seconds to wait for authentication in an external browser (default: 120). | -| VALIDATE_DEFAULT_PARAMETERS | No | Whether DB, SCHEMA and WAREHOUSE should be verified when making connection. Default to be true. | -| PRIVATE_KEY_FILE | Depends | The path to the private key file to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt | -| PRIVATE_KEY_PWD | No | The passphrase to use for decrypting the private key, if the key is encrypted. | -| PRIVATE_KEY | Depends | The private key to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt.
If the private key value includes any equal signs (=), make sure to replace each equal sign with two signs (==) to ensure that the connection string is parsed correctly. | -| TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | -| INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | -| USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | -| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | -| PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | -| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | -| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.

This parameter was introduced in v2.0.4. | -| FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exeeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | -| CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | +| VALIDATE_DEFAULT_PARAMETERS | No | Whether DB, SCHEMA and WAREHOUSE should be verified when making connection. Default to be true. | +| PRIVATE_KEY_FILE | Depends | The path to the private key file to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt | +| PRIVATE_KEY_PWD | No | The passphrase to use for decrypting the private key, if the key is encrypted. | +| PRIVATE_KEY | Depends | The private key to use for key-pair authentication. Must be used in combination with AUTHENTICATOR=snowflake_jwt.
If the private key value includes any equal signs (=), make sure to replace each equal sign with two signs (==) to ensure that the connection string is parsed correctly. | +| TOKEN | Depends | The OAuth token to use for OAuth authentication. Must be used in combination with AUTHENTICATOR=oauth. | +| INSECUREMODE | No | Set to true to disable the certificate revocation list check. Default is false. | +| USEPROXY | No | Set to true if you need to use a proxy server. The default value is false.

This parameter was introduced in v2.0.4. | +| PROXYHOST | Depends | The hostname of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYPORT | Depends | The port number of the proxy server.

If USEPROXY is set to `true`, you must set this parameter.

This parameter was introduced in v2.0.4. | +| PROXYUSER | No | The username for authenticating to the proxy server.

This parameter was introduced in v2.0.4. | +| PROXYPASSWORD | Depends | The password for authenticating to the proxy server.

If USEPROXY is `true` and PROXYUSER is set, you must set this parameter.

This parameter was introduced in v2.0.4. | +| NONPROXYHOSTS | No | The list of hosts that the driver should connect to directly, bypassing the proxy server. Separate the hostnames with a pipe symbol (\|). You can also use an asterisk (`*`) as a wildcard.

This parameter was introduced in v2.0.4. | +| FILE_TRANSFER_MEMORY_THRESHOLD | No | The maximum number of bytes to store in memory used in order to provide a file encryption. If encrypting/decrypting file size exeeds provided value a temporary file will be created and the work will be continued in the temporary file instead of memory.
If no value provided 1MB will be used as a default value (that is 1048576 bytes).
It is possible to configure any integer value bigger than zero representing maximal number of bytes to reside in memory. | +| CLIENT_CONFIG_FILE | No | The location of the client configuration json file. In this file you can configure easy logging feature. | +| ALLOWUNDERSCORESINHOST | No | Specifies whether to allow underscores in account names. This impacts PrivateLink customers whose account names contain underscores. In this situation, you must override the default value by setting allowUnderscoresInHost to true. |
diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index c13cd684b..13205c52d 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -523,7 +523,7 @@ public void TestConnectionFailFast() using (var conn = new SnowflakeDbConnection()) { // Just a way to get a 404 on the login request and make sure there are no retry - string invalidConnectionString = "host=docs.microsoft.com;" + string invalidConnectionString = "host=learn.microsoft.com;" + "connection_timeout=0;account=testFailFast;user=testFailFast;password=testFailFast;"; conn.ConnectionString = invalidConnectionString; @@ -549,7 +549,7 @@ public void TestEnableRetry() { using (var conn = new SnowflakeDbConnection()) { - string invalidConnectionString = "host=docs.microsoft.com;" + string invalidConnectionString = "host=learn.microsoft.com;" + "connection_timeout=0;account=testFailFast;user=testFailFast;password=testFailFast;disableretry=true;forceretryon404=true"; conn.ConnectionString = invalidConnectionString; @@ -1866,7 +1866,7 @@ public void TestAsyncConnectionFailFast() using (var conn = new SnowflakeDbConnection()) { // Just a way to get a 404 on the login request and make sure there are no retry - string invalidConnectionString = "host=docs.microsoft.com;" + string invalidConnectionString = "host=learn.microsoft.com;" + "connection_timeout=0;account=testFailFast;user=testFailFast;password=testFailFast;"; conn.ConnectionString = invalidConnectionString; diff --git a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs index cd72bffce..698501b25 100644 --- a/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFSessionPropertyTest.cs @@ -78,6 +78,7 @@ public static IEnumerable ConnectionStringTestCases() string defMaxHttpRetries = "7"; string defIncludeRetryReason = "true"; string defDisableQueryContextCache = "false"; + string defAllowUnderscoresInHost = "false"; var simpleTestCase = new TestCase() { @@ -103,7 +104,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } } }; var testCaseWithBrowserResponseTimeout = new TestCase() @@ -129,7 +131,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } } }; var testCaseWithProxySettings = new TestCase() @@ -158,7 +161,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};useProxy=true;proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -189,7 +193,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};proxyHost=proxy.com;proxyPort=1234;nonProxyHosts=localhost" @@ -219,7 +224,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.FILE_TRANSFER_MEMORY_THRESHOLD, "25" }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } } }; var testCaseWithIncludeRetryReason = new TestCase() @@ -246,7 +252,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, "false" }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } } }; var testCaseWithDisableQueryContextCache = new TestCase() @@ -272,7 +279,8 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, "true" } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, "true" }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } }, ConnectionString = $"ACCOUNT={defAccount};USER={defUser};PASSWORD={defPassword};DISABLEQUERYCONTEXTCACHE=true" @@ -302,7 +310,64 @@ public static IEnumerable ConnectionStringTestCases() { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, - { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache } + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + } + }; + var testCaseUnderscoredAccountName = new TestCase() + { + ConnectionString = $"ACCOUNT=prefix_{defAccount};USER={defUser};PASSWORD={defPassword};", + ExpectedProperties = new SFSessionProperties() + { + { SFSessionProperty.ACCOUNT, $"prefix_{defAccount}" }, + { SFSessionProperty.USER, defUser }, + { SFSessionProperty.HOST, $"prefix-{defAccount}.snowflakecomputing.com" }, + { SFSessionProperty.AUTHENTICATOR, defAuthenticator }, + { SFSessionProperty.SCHEME, defScheme }, + { SFSessionProperty.CONNECTION_TIMEOUT, defConnectionTimeout }, + { SFSessionProperty.PASSWORD, defPassword }, + { SFSessionProperty.PORT, defPort }, + { SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" }, + { SFSessionProperty.USEPROXY, "false" }, + { SFSessionProperty.INSECUREMODE, "false" }, + { SFSessionProperty.DISABLERETRY, "false" }, + { SFSessionProperty.FORCERETRYON404, "false" }, + { SFSessionProperty.CLIENT_SESSION_KEEP_ALIVE, "false" }, + { SFSessionProperty.FORCEPARSEERROR, "false" }, + { SFSessionProperty.BROWSER_RESPONSE_TIMEOUT, defBrowserResponseTime }, + { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, + { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, + { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, defAllowUnderscoresInHost } + } + }; + var testCaseUnderscoredAccountNameWithEnabledAllowUnderscores = new TestCase() + { + ConnectionString = $"ACCOUNT=prefix_{defAccount};USER={defUser};PASSWORD={defPassword};allowUnderscoresInHost=true;", + ExpectedProperties = new SFSessionProperties() + { + { SFSessionProperty.ACCOUNT, $"prefix_{defAccount}" }, + { SFSessionProperty.USER, defUser }, + { SFSessionProperty.HOST, $"prefix_{defAccount}.snowflakecomputing.com" }, + { SFSessionProperty.AUTHENTICATOR, defAuthenticator }, + { SFSessionProperty.SCHEME, defScheme }, + { SFSessionProperty.CONNECTION_TIMEOUT, defConnectionTimeout }, + { SFSessionProperty.PASSWORD, defPassword }, + { SFSessionProperty.PORT, defPort }, + { SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS, "true" }, + { SFSessionProperty.USEPROXY, "false" }, + { SFSessionProperty.INSECUREMODE, "false" }, + { SFSessionProperty.DISABLERETRY, "false" }, + { SFSessionProperty.FORCERETRYON404, "false" }, + { SFSessionProperty.CLIENT_SESSION_KEEP_ALIVE, "false" }, + { SFSessionProperty.FORCEPARSEERROR, "false" }, + { SFSessionProperty.BROWSER_RESPONSE_TIMEOUT, defBrowserResponseTime }, + { SFSessionProperty.RETRY_TIMEOUT, defRetryTimeout }, + { SFSessionProperty.MAXHTTPRETRIES, defMaxHttpRetries }, + { SFSessionProperty.INCLUDERETRYREASON, defIncludeRetryReason }, + { SFSessionProperty.DISABLEQUERYCONTEXTCACHE, defDisableQueryContextCache }, + { SFSessionProperty.ALLOWUNDERSCORESINHOST, "true" } } }; return new TestCase[] @@ -314,7 +379,9 @@ public static IEnumerable ConnectionStringTestCases() testCaseWithFileTransferMaxBytesInMemory, testCaseWithIncludeRetryReason, testCaseWithDisableQueryContextCache, - testCaseComplicatedAccountName + testCaseComplicatedAccountName, + testCaseUnderscoredAccountName, + testCaseUnderscoredAccountNameWithEnabledAllowUnderscores }; } diff --git a/Snowflake.Data/Core/Session/SFSessionProperty.cs b/Snowflake.Data/Core/Session/SFSessionProperty.cs index b07015a88..9a090cdb2 100755 --- a/Snowflake.Data/Core/Session/SFSessionProperty.cs +++ b/Snowflake.Data/Core/Session/SFSessionProperty.cs @@ -90,7 +90,9 @@ internal enum SFSessionProperty [SFSessionPropertyAttr(required = false, defaultValue = "false")] DISABLEQUERYCONTEXTCACHE, [SFSessionPropertyAttr(required = false)] - CLIENT_CONFIG_FILE + CLIENT_CONFIG_FILE, + [SFSessionPropertyAttr(required = false, defaultValue = "false")] + ALLOWUNDERSCORESINHOST } class SFSessionPropertyAttr : Attribute @@ -255,12 +257,20 @@ internal static SFSessionProperties parseConnectionString(String connectionStrin checkSessionProperties(properties); ValidateFileTransferMaxBytesInMemoryProperty(properties); ValidateAccountDomain(properties); + + var allowUnderscoresInHost = ParseAllowUnderscoresInHost(properties); // compose host value if not specified if (!properties.ContainsKey(SFSessionProperty.HOST) || (0 == properties[SFSessionProperty.HOST].Length)) { - string hostName = String.Format("{0}.snowflakecomputing.com", properties[SFSessionProperty.ACCOUNT]); + var compliantAccountName = properties[SFSessionProperty.ACCOUNT]; + if (!allowUnderscoresInHost && compliantAccountName.Contains('_')) + { + compliantAccountName = compliantAccountName.Replace('_', '-'); + logger.Info($"Replacing _ with - in the account name. Old: {properties[SFSessionProperty.ACCOUNT]}, new: {compliantAccountName}."); + } + var hostName = $"{compliantAccountName}.snowflakecomputing.com"; // Remove in case it's here but empty properties.Remove(SFSessionProperty.HOST); properties.Add(SFSessionProperty.HOST, hostName); @@ -378,6 +388,23 @@ private static bool IsRequired(SFSessionProperty sessionProperty, SFSessionPrope return sessionProperty.GetAttribute().required; } } + + private static bool ParseAllowUnderscoresInHost(SFSessionProperties properties) + { + var allowUnderscoresInHost = bool.Parse(SFSessionProperty.ALLOWUNDERSCORESINHOST.GetAttribute().defaultValue); + if (!properties.TryGetValue(SFSessionProperty.ALLOWUNDERSCORESINHOST, out var property)) + return allowUnderscoresInHost; + try + { + allowUnderscoresInHost = bool.Parse(property); + } + catch (Exception e) + { + logger.Warn("Unable to parse property 'allowUnderscoresInHost'", e); + } + + return allowUnderscoresInHost; + } } public static class EnumExtensions