Repository: snowflakedb/gosnowflake
Pull request #343 (2020-10-21 20:58:49)

SNOW-209090 Support opening connection with Config struct
Description
Add the ability to open a connection using a specific Config struct.
Over time, additional non-string representation config options will be implemented,
so we would like the ability to specify the Config directly vs always stringify into a DSN.

Testing
All existing unit tests hit new code path.

### Checklist
- [x] Code compiles correctly
- [x] Run ``make fmt`` to fix inconsistent formats
- [x] Run ``make lint`` to get lint errors and fix all of them
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [x] Extended the README / documentation, if necessary



Patch:
diff --git a/connector.go b/connector.go
new file mode 100644
index 0000000..a70241a
--- /dev/null
+++ b/connector.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"context"
+	"database/sql/driver"
+)
+
+// InternalSnowflakeDriver is the interface for an internal Snowflake driver
+type InternalSnowflakeDriver interface {
+	Open(dsn string) (driver.Conn, error)
+	OpenWithConfig(ctx context.Context, config Config) (driver.Conn, error)
+}
+
+// Connector creates Driver with the specified Config
+type Connector struct {
+	driver InternalSnowflakeDriver
+	cfg    Config
+}
+
+// NewConnector creates a new connector with the given SnowflakeDriver and Config.
+func NewConnector(driver InternalSnowflakeDriver, config Config) Connector {
+	return Connector{driver, config}
+}
+
+// Connect creates a new connection.
+func (t Connector) Connect(ctx context.Context) (driver.Conn, error) {
+	cfg := t.cfg
+	err := fillMissingConfigParameters(&cfg)
+	if err != nil {
+		return nil, err
+	}
+	return t.driver.OpenWithConfig(ctx, cfg)
+}
+
+// Driver creates a new driver.
+func (t Connector) Driver() driver.Driver {
+	return t.driver
+}
diff --git a/doc.go b/doc.go
index 52a345e..16292be 100644
--- a/doc.go
+++ b/doc.go
@@ -125,6 +125,8 @@ A complete connection string looks similar to the following:
 Session-level parameters can also be set by using the SQL command "ALTER SESSION"
 (https://docs.snowflake.com/en/sql-reference/sql/alter-session.html).
 
+Alternatively, use OpenWithConfig() function to create a database handle with the specified Config.
+
 Proxy
 
 The Go Snowflake Driver honors the environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY for the forward proxy setting.
diff --git a/driver.go b/driver.go
index e77f6cd..9bbcdc2 100644
--- a/driver.go
+++ b/driver.go
@@ -17,16 +17,22 @@ type SnowflakeDriver struct {
 // Open creates a new connection.
 func (d SnowflakeDriver) Open(dsn string) (driver.Conn, error) {
 	logger.Info("Open")
+	ctx := context.TODO()
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		return nil, err
+	}
+	return d.OpenWithConfig(ctx, *cfg)
+}
+
+// OpenWithConfig creates a new connection with the given Config.
+func (d SnowflakeDriver) OpenWithConfig(ctx context.Context, config Config) (driver.Conn, error) {
+	logger.Info("OpenWithConfig")
 	var err error
 	sc := &snowflakeConn{
 		SequenceCounter: 0,
-		ctx:             context.TODO(),
-	}
-
-	sc.cfg, err = ParseDSN(dsn)
-	if err != nil {
-		sc.cleanup()
-		return nil, err
+		ctx: ctx,
+		cfg: &config,
 	}
 	st := SnowflakeTransport
 	if sc.cfg.InsecureMode {
diff --git a/dsn.go b/dsn.go
index d7b2b37..4817cc4 100644
--- a/dsn.go
+++ b/dsn.go
@@ -413,7 +413,7 @@ func fillMissingConfigParameters(cfg *Config) error {
 	return nil
 }
 
-// transformAccountToHost transforms host to accout name
+// transformAccountToHost transforms host to account name
 func transformAccountToHost(cfg *Config) (err error) {
 	if cfg.Port == 0 && !strings.HasSuffix(cfg.Host, defaultDomain) && cfg.Host != "" {
 		// account name is specified instead of host:port


Test patch:
diff --git a/connector_test.go b/connector_test.go
new file mode 100644
index 0000000..4bf46ba
--- /dev/null
+++ b/connector_test.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2020 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"context"
+	"database/sql/driver"
+	"reflect"
+	"testing"
+)
+
+type noopTestDriver struct {
+	config Config
+	conn   *snowflakeConn
+}
+
+func (d noopTestDriver) Open(_ string) (driver.Conn, error) {
+	return nil, nil
+}
+
+func (d noopTestDriver) OpenWithConfig(_ context.Context, config Config) (driver.Conn, error) {
+	d.config = config
+	return d.conn, nil
+}
+
+func TestConnector(t *testing.T) {
+	conn := snowflakeConn{}
+	mock := noopTestDriver{conn: &conn}
+	createDSN("UTC")
+	config, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatalf("Failed to parse dsn %s", dsn)
+	}
+	connector := NewConnector(mock, *config)
+	connection, err := connector.Connect(context.Background())
+	if err != nil {
+		t.Fatalf("Connect error %s", err)
+	}
+	if connection != &conn {
+		t.Fatalf("Connection mismatch %s", connection)
+	}
+	fillMissingConfigParameters(config)
+	if reflect.DeepEqual(config, mock.config) {
+		t.Fatalf("Config should be equal, expected %v, actual %v", config, mock.config)
+	}
+	if connector.Driver() == nil {
+		t.Fatalf("Missing driver")
+	}
+}
diff --git a/driver_test.go b/driver_test.go
index d97012e..b77c3a0 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2388,6 +2388,19 @@ func TestPingInvalidHost(t *testing.T) {
 	}
 }
 
+func TestOpenWithConfig(t *testing.T) {
+	config, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatalf("failed to parse dsn. dsn: %v, err: %v", dsn, err)
+	}
+	driver := SnowflakeDriver{}
+	db, err := driver.OpenWithConfig(context.Background(), *config)
+	if err != nil {
+		t.Fatalf("failed to open with config. config: %v, err: %v", config, err)
+	}
+	db.Close()
+}
+
 func createDSNWithClientSessionKeepAlive() {
 	dsn = fmt.Sprintf("%s:%s@%s/%s/%s", user, pass, host, dbname, schemaname)