Repository: firebolt-db/firebolt-go-sdk
Pull request #135: fix: FIR-44362 split driver client options 
Author: login: stepansergeevitch
Created: 2025-03-12T13:03:13Z | Merged: 2025-03-13T11:48:38Z

A couple of improvements for pg_fire
1. Hardcoded newVersion value for every client type. Otherwise we got into a situation, where we has ClientImpl have newVersion=false, which caused issues when streaming
2. Split driver client options into three different function for a more granular control


Diff:
diff --git a/client/client.go b/client/client.go
index c92baefd..32aeb5d7 100644
--- a/client/client.go
+++ b/client/client.go
@@ -52,7 +52,7 @@ func MakeClient(settings *types.FireboltSettings, apiEndpoint string) (*ClientIm
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
-			newVersion:   settings.NewVersion,
+			newVersion:   true,
 		},
 		AccountName: settings.AccountName,
 	}
diff --git a/client/client_v0.go b/client/client_v0.go
index 574fb825..d0399648 100644
--- a/client/client_v0.go
+++ b/client/client_v0.go
@@ -25,7 +25,7 @@ func MakeClientV0(settings *types.FireboltSettings, apiEndpoint string) (*Client
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
-			newVersion:   settings.NewVersion,
+			newVersion:   false,
 		},
 	}
 	client.ParameterGetter = client.getQueryParams
diff --git a/driver_options.go b/driver_options.go
index 024d4776..cc8e8e1f 100644
--- a/driver_options.go
+++ b/driver_options.go
@@ -21,29 +21,60 @@ func WithDatabaseName(databaseName string) driverOption {
 	}
 }
 
-// WithClientParams defines client parameters for the driver
-func WithClientParams(accountID string, token string, userAgent string) driverOption {
+// WithAccountID defines account ID for the driver
+func WithAccountID(accountID string) driverOption {
 	return func(d *FireboltDriver) {
 		if d.cachedParams == nil {
 			d.cachedParams = map[string]string{}
 		}
-		// Put account_id in cachedParams for it to work both with engines v1 and v2
 		if accountID != "" {
 			d.cachedParams["account_id"] = accountID
 		}
+	}
+}
 
-		cl := &client.ClientImpl{
-			ConnectedToSystemEngine: true,
+func withClientOption(setter func(baseClient *client.BaseClient)) driverOption {
+	return func(d *FireboltDriver) {
+		if d.client != nil {
+			if clientImpl, ok := d.client.(*client.ClientImpl); ok {
+				setter(&clientImpl.BaseClient)
+			} else if clientImplV0, ok := d.client.(*client.ClientImplV0); ok {
+				setter(&clientImplV0.BaseClient)
+			}
+		} else {
+			cl := &client.ClientImpl{
+				ConnectedToSystemEngine: true,
+				BaseClient:              client.BaseClient{},
+			}
+			cl.ParameterGetter = cl.GetQueryParams
+			setter(&cl.BaseClient)
+			d.client = cl
 		}
+	}
+}
 
-		cl.UserAgent = userAgent
-
-		cl.ParameterGetter = cl.GetQueryParams
-		cl.AccessTokenGetter = func() (string, error) {
+// WithToken defines token for the driver
+func WithToken(token string) driverOption {
+	return withClientOption(func(baseClient *client.BaseClient) {
+		baseClient.AccessTokenGetter = func() (string, error) {
 			return token, nil
 		}
+	})
+}
+
+// WithUserAgent defines user agent for the driver
+func WithUserAgent(userAgent string) driverOption {
+	return withClientOption(func(baseClient *client.BaseClient) {
+		baseClient.UserAgent = userAgent
+	})
+}
 
-		d.client = cl
+// WithClientParams defines client parameters for the driver
+func WithClientParams(accountID string, token string, userAgent string) driverOption {
+	return func(d *FireboltDriver) {
+		WithAccountID(accountID)(d)
+		WithToken(token)(d)
+		WithUserAgent(userAgent)(d)
 	}
 }
 
diff --git a/driver_options_integration_test.go b/driver_options_integration_test.go
index 6c9ff0a0..ca630d31 100644
--- a/driver_options_integration_test.go
+++ b/driver_options_integration_test.go
@@ -72,3 +72,63 @@ func TestFireboltConnectorWithOptions(t *testing.T) {
 	utils.AssertEqual(len(values), 1, t, "returned more that one value")
 	utils.AssertEqual(values[0], int32(1), t, "result is not 1")
 }
+
+func TestFireboltConnectorWithASeparatedOptions(t *testing.T) {
+	cl, err := client.ClientFactory(&types.FireboltSettings{
+		ClientID:     clientIdMock,
+		ClientSecret: clientSecretMock,
+		AccountName:  accountName,
+		EngineName:   engineNameMock,
+		Database:     databaseMock,
+		NewVersion:   true,
+	}, client.GetHostNameURL())
+	if err != nil {
+		t.Errorf("failed to authenticate with client id %s: %v", clientIdMock, err)
+		t.FailNow()
+	}
+	token, err := cl.(*client.ClientImpl).AccessTokenGetter()
+	if err != nil {
+		t.Errorf("failed to get access token: %v", err)
+		t.FailNow()
+	}
+
+	userAgent := "test user agent"
+
+	engineUrl, engineParameters, err := cl.GetConnectionParameters(context.TODO(), engineNameMock, databaseMock)
+	if err != nil {
+		t.Errorf("failed to get system engine url: %v", err)
+		t.FailNow()
+	}
+
+	accountID, _ := engineParameters["account_id"]
+
+	conn := FireboltConnectorWithOptions(
+		WithEngineUrl(engineUrl),
+		WithDatabaseName(databaseMock),
+		WithAccountID(accountID),
+		WithToken(token),
+		WithUserAgent(userAgent),
+	)
+
+	resp, err := conn.client.Query(context.Background(), conn.engineUrl, "SELECT 1", nil, client.ConnectionControl{})
+	if err != nil {
+		t.Errorf("failed unexpectedly with: %v", err)
+		t.FailNow()
+	}
+
+	rows := rows.InMemoryRows{}
+	if err := rows.ProcessAndAppendResponse(resp); err != nil {
+		t.Errorf("failed to process response: %v", err)
+		t.FailNow()
+	}
+
+	var values []driver.Value = make([]driver.Value, len(rows.Columns()))
+
+	if err := rows.Next(values); err != nil {
+		t.Errorf("failed to get result: %v", err)
+		t.FailNow()
+	}
+
+	utils.AssertEqual(len(values), 1, t, "returned more that one value")
+	utils.AssertEqual(values[0], int32(1), t, "result is not 1")
+}
diff --git a/driver_options_test.go b/driver_options_test.go
index 82e05ce7..c42be88d 100644
--- a/driver_options_test.go
+++ b/driver_options_test.go
@@ -38,3 +38,36 @@ func TestDriverOptions(t *testing.T) {
 
 	utils.AssertEqual(tok, token, t, "token getter returned wrong token")
 }
+
+func TestDriverOptionsSeparateClientParams(t *testing.T) {
+	engineUrl := "https://engine.url"
+	databaseName := "database_name"
+	accountID := "account-id"
+	token := "1234567890"
+	userAgent := "UA Test"
+
+	conn := FireboltConnectorWithOptions(
+		WithEngineUrl(engineUrl),
+		WithDatabaseName(databaseName),
+		WithAccountID(accountID),
+		WithToken(token),
+		WithUserAgent(userAgent),
+	)
+
+	utils.AssertEqual(conn.engineUrl, engineUrl, t, "engineUrl is invalid")
+	utils.AssertEqual(conn.cachedParameters["database"], databaseName, t, "databaseName is invalid")
+
+	cl, ok := conn.client.(*client.ClientImpl)
+	utils.AssertEqual(ok, true, t, "client is not *ClientImpl")
+
+	connectionAccountID := conn.cachedParameters["account_id"]
+	utils.AssertEqual(connectionAccountID, accountID, t, "accountID is invalid")
+	utils.AssertEqual(cl.UserAgent, userAgent, t, "userAgent is invalid")
+
+	tok, err := cl.AccessTokenGetter()
+	if err != nil {
+		t.Errorf("token getter returned an error: %v", err)
+	}
+
+	utils.AssertEqual(tok, token, t, "token getter returned wrong token")
+}


Reviews:
user: login: ptiurin
body: apart from that looks good
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:25:29Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:39:14Z
user: login: ptiurin
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T11:47:28Z

Review comments:
diff_hunk: @@ -21,29 +21,74 @@ func WithDatabaseName(databaseName string) driverOption {
 	}
 }
 
-// WithClientParams defines client parameters for the driver
-func WithClientParams(accountID string, token string, userAgent string) driverOption {
+// WithAccountID defines account ID for the driver
+func WithAccountID(accountID string) driverOption {
 	return func(d *FireboltDriver) {
 		if d.cachedParams == nil {
 			d.cachedParams = map[string]string{}
 		}
-		// Put account_id in cachedParams for it to work both with engines v1 and v2
 		if accountID != "" {
 			d.cachedParams["account_id"] = accountID
 		}
+	}
+}
 
-		cl := &client.ClientImpl{
-			ConnectedToSystemEngine: true,
+// WithToken defines token for the driver
+func WithToken(token string) driverOption {
+	return func(d *FireboltDriver) {
+		if d.client != nil {
+			if clientImpl, ok := d.client.(*client.ClientImpl); ok {
+				clientImpl.AccessTokenGetter = func() (string, error) {
+					return token, nil
+				}
+			} else if clientImplV0, ok := d.client.(*client.ClientImplV0); ok {
+				clientImplV0.AccessTokenGetter = func() (string, error) {
+					return token, nil
+				}
+			}
+		} else {
+			cl := &client.ClientImpl{
+				ConnectedToSystemEngine: true,
+				BaseClient: client.BaseClient{
+					AccessTokenGetter: func() (string, error) {
+						return token, nil
+					},
+				},
+			}
+			cl.ParameterGetter = cl.GetQueryParams
path: driver_options.go
user: login: ptiurin
body: Looks like there's logic duplication with WithUserAgent. I think it should be possible to make a generic method for this, passing in a method that sets the parameter that we require?
e.g. 
```
func WithClientOption(setter func(*client.BaseClient)) driverOption {
        if d.client != nil {
            if clientImpl, ok := d.client.(*client.ClientImpl); ok {
                setter(&clientImpl.BaseClient)
            } else if clientImplV0, ok := d.client.(*client.ClientImplV0); ok {
                setter(&clientImplV0.BaseClient)
            }
...
```
And call it with
```
func WithToken(token string) driverOption {
    return WithClientOption(func(bc *client.BaseClient) {
        bc.AccessTokenGetter = func() (string, error) {
            return token, nil
        }
    })
}
```
wdyt?

created_at: 2025-03-12T13:24:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,29 +21,74 @@ func WithDatabaseName(databaseName string) driverOption {
 	}
 }
 
-// WithClientParams defines client parameters for the driver
-func WithClientParams(accountID string, token string, userAgent string) driverOption {
+// WithAccountID defines account ID for the driver
+func WithAccountID(accountID string) driverOption {
 	return func(d *FireboltDriver) {
 		if d.cachedParams == nil {
 			d.cachedParams = map[string]string{}
 		}
-		// Put account_id in cachedParams for it to work both with engines v1 and v2
 		if accountID != "" {
 			d.cachedParams["account_id"] = accountID
 		}
+	}
+}
 
-		cl := &client.ClientImpl{
-			ConnectedToSystemEngine: true,
+// WithToken defines token for the driver
+func WithToken(token string) driverOption {
+	return func(d *FireboltDriver) {
+		if d.client != nil {
+			if clientImpl, ok := d.client.(*client.ClientImpl); ok {
+				clientImpl.AccessTokenGetter = func() (string, error) {
+					return token, nil
+				}
+			} else if clientImplV0, ok := d.client.(*client.ClientImplV0); ok {
+				clientImplV0.AccessTokenGetter = func() (string, error) {
+					return token, nil
+				}
+			}
+		} else {
+			cl := &client.ClientImpl{
+				ConnectedToSystemEngine: true,
+				BaseClient: client.BaseClient{
+					AccessTokenGetter: func() (string, error) {
+						return token, nil
+					},
+				},
+			}
+			cl.ParameterGetter = cl.GetQueryParams
path: driver_options.go
user: login: stepansergeevitch
body: Makes sense, updated the code
created_at: 2025-03-13T11:39:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: ptiurin
created_at: 2025-03-12T13:27:13Z
body: Also, probably let's try to summarise the improvements here instead of saying `fix: FIR-44362 go sdk improvements` in the commit. I think the main change was splitting the options so `fix: FIR-44362 split driver client options` could do the trick.
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-13T11:39:32Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=firebolt-db_firebolt-go-sdk&pullRequest=135) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=firebolt-db_firebolt-go-sdk&pullRequest=135&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=firebolt-db_firebolt-go-sdk&pullRequest=135&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=firebolt-db_firebolt-go-sdk&pullRequest=135&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_firebolt-go-sdk&pullRequest=135&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_firebolt-go-sdk&pullRequest=135&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=firebolt-db_firebolt-go-sdk&pullRequest=135)


author_association: NONE