Repository: snowflakedb/gosnowflake
Pull request #1434 (2025-06-05 07:57:59)

SNOW-2135451 Support snake_case connection parameters
# Support snake_case connection parameters

## Problem
The Go driver currently doesn't support reading snake_case parameters from connection configuration files (e.g., connections.toml). This is problematic in Snowpark Container Services (SPCS) where connection parameters are automatically generated in snake_case format, preventing the use of auto-configuration features.

## Solution
Modified the connection parameter parsing logic to handle both camelCase and snake_case parameter names. This change maintains backward compatibility with camelCase while adding support for snake_case variants, enabling seamless auto-configuration in SPCS environments.

For example:
- `client_timeout` and `clientTimeout`
- `login_timeout` and `loginTimeout`
- `disable_ocsp_checks` and `disableOcspChecks`

### Checklist
- [ ] Created tests which fail without the change (if possible)
- [X] Extended the README / documentation, if necessary


github-actions[bot]: <br/>Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our [Contributor License Agreement](https://github.com/snowflakedb/CLA/blob/main/README.md) before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.<br/>
   - - -
   I have read the CLA Document and I hereby sign the CLA
   - - -
   <sub>You can retrigger this bot by commenting **recheck** in this Pull Request. </sub><sub>Posted by the **CLA Assistant Lite bot**.</sub>

Patch:
diff --git a/connection_configuration.go b/connection_configuration.go
index d26acc3..fb292d6 100644
--- a/connection_configuration.go
+++ b/connection_configuration.go
@@ -82,7 +82,12 @@ func parseToml(cfg *Config, connectionMap map[string]interface{}) error {
 func handleSingleParam(cfg *Config, key string, value interface{}) error {
 	var err error
 	var v, tokenPath string
-	switch strings.ToLower(key) {
+
+	// We normalize the key to handle both snake_case and camelCase.
+	normalizedKey := strings.ReplaceAll(strings.ToLower(key), "_", "")
+
+	// the cases in switch statement should be in lower case and no _
+	switch normalizedKey {
 	case "user", "username":
 		cfg.User, err = parseString(value)
 	case "password":
@@ -178,24 +183,24 @@ func handleSingleParam(cfg *Config, key string, value interface{}) error {
 		cfg.DisableConsoleLogin, err = parseConfigBool(value)
 	case "disablesamlurlcheck":
 		cfg.DisableSamlURLCheck, err = parseConfigBool(value)
-	case "oauth_authorization_url":
+	case "oauthauthorizationurl":
 		cfg.OauthAuthorizationURL, err = parseString(value)
-	case "oauth_client_id":
+	case "oauthclientid":
 		cfg.OauthClientID, err = parseString(value)
-	case "oauth_client_secret":
+	case "oauthclientsecret":
 		cfg.OauthClientSecret, err = parseString(value)
-	case "oauth_token_request_url":
+	case "oauthtokenrequesturl":
 		cfg.OauthTokenRequestURL, err = parseString(value)
-	case "oauth_redirect_uri":
+	case "oauthredirecturi":
 		cfg.OauthRedirectURI, err = parseString(value)
-	case "oauth_scope":
+	case "oauthscope":
 		cfg.OauthScope, err = parseString(value)
-	case "workload_identity_provider":
+	case "workloadidentityprovider":
 		cfg.WorkloadIdentityProvider, err = parseString(value)
-	case "workload_identity_entra_resource":
+	case "workloadidentityentraresource":
 		cfg.WorkloadIdentityEntraResource, err = parseString(value)
 
-	case "token_file_path":
+	case "tokenfilepath":
 		tokenPath, err = parseString(value)
 		if err = checkParsingError(err, key, value); err != nil {
 			return err


Test patch:
diff --git a/connection_configuration_test.go b/connection_configuration_test.go
index c1b48ed..3e8b50d 100644
--- a/connection_configuration_test.go
+++ b/connection_configuration_test.go
@@ -95,6 +95,18 @@ func TestLoadConnectionConfigForOAuth(t *testing.T) {
 	assertEqualE(t, cfg.DisableOCSPChecks, true)
 }
 
+func TestLoadConnectionConfigForSnakeCaseConfiguration(t *testing.T) {
+	err := os.Chmod("./test_data/connections.toml", 0600)
+	assertNilF(t, err, "The error occurred because you cannot change the file permission")
+
+	os.Setenv(snowflakeHome, "./test_data")
+	os.Setenv(snowflakeConnectionName, "snake-case")
+
+	cfg, err := loadConnectionConfig()
+	assertNilF(t, err, "The error should not occur")
+	assertEqualE(t, cfg.OCSPFailOpen, OCSPFailOpenTrue)
+}
+
 func TestReadTokenValueWithTokenFilePath(t *testing.T) {
 	err := os.Chmod("./test_data/connections.toml", 0600)
 	assertNilF(t, err, "The error occurred because you cannot change the file permission")
@@ -205,23 +217,24 @@ func TestParseToml(t *testing.T) {
 		{
 			testParams: []string{"user", "password", "host", "account", "warehouse", "database",
 				"schema", "role", "region", "protocol", "passcode", "application", "token",
-				"tracing", "tmpDirPath", "clientConfigFile", "oauth_authorization_url", "oauth_client_id",
+				"tracing", "tmpDirPath", "tmp_dir_path", "clientConfigFile", "client_config_file", "oauth_authorization_url", "oauth_client_id",
 				"oauth_client_secret", "oauth_token_request_url", "oauth_redirect_uri", "oauth_scope",
 				"workload_identity_provider", "workload_identity_entra_resource"},
 			values: []interface{}{"value"},
 		},
 		{
-			testParams: []string{"privatekey"},
+			testParams: []string{"privatekey", "private_key"},
 			values:     []interface{}{generatePKCS8StringSupress(testPrivKey)},
 		},
 		{
-			testParams: []string{"port", "maxRetryCount", "clientTimeout", "jwtClientTimeout", "loginTimeout",
-				"requestTimeout", "jwtTimeout", "externalBrowserTimeout"},
+			testParams: []string{"port", "maxRetryCount", "max_retry_count", "clientTimeout", "client_timeout", "jwtClientTimeout", "jwt_client_timeout", "loginTimeout",
+				"login_timeout", "requestTimeout", "request_timeout", "jwtTimeout", "jwt_timeout", "externalBrowserTimeout", "external_browser_timeout"},
 			values: []interface{}{"300", 500},
 		},
 		{
-			testParams: []string{"ocspFailOpen", "insecureMode", "PasscodeInPassword", "validateDEFAULTParameters", "clientRequestMFAtoken",
-				"clientStoreTemporaryCredential", "disableQueryContextCache", "includeRetryReason", "disableConsoleLogin", "disableSamlUrlCheck"},
+			testParams: []string{"ocspFailOpen", "ocsp_fail_open", "insecureMode", "insecure_mode", "PasscodeInPassword", "passcode_in_password", "validateDEFAULTParameters", "validate_default_parameters",
+				"clientRequestMFAtoken", "client_request_mfa_token", "clientStoreTemporaryCredential", "client_store_temporary_credential", "disableQueryContextCache", "disable_query_context_cache", "disable_ocsp_checks",
+				"includeRetryReason", "include_retry_reason", "disableConsoleLogin", "disable_console_login", "disableSamlUrlCheck", "disable_saml_url_check"},
 			values: []interface{}{true, "true", false, "false"},
 		},
 	}
diff --git a/test_data/connections.toml b/test_data/connections.toml
index 1d7aa5e..4b38b55 100644
--- a/test_data/connections.toml
+++ b/test_data/connections.toml
@@ -45,4 +45,15 @@ schema = 'test_default_go'
 protocol = 'https'
 authenticator = 'oauth'
 token_file_path = './test_data/snowflake/session/token'
-insecureMode = true
\ No newline at end of file
+insecureMode = true
+
+[snake-case]
+account = 'snowdriverswarsaw.us-west-2.aws'
+user = 'test_default_user'
+password = 'test_default_pass'
+warehouse = 'testw_default'
+database = 'test_default_db'
+schema = 'test_default_go'
+protocol = 'https'
+port = '300'
+ocsp_fail_open=true