database/sql: allow OpenConnector in a driver.Driver interface

While driver.Connector was previously added to allow non-string
connection arguments and access to the context, most users of
the sql package will continue to rely on a string DSN.

Allow drivers to implement a string DSN to Connector interface
that both allows a single parsing of the string DSN and uses
the Connector interface which passes available context to
the driver dialer.

Fixes #22713

Change-Id: Ia0b862262f4c4670effe2538d0d6d43733fea18d
Reviewed-on: https://go-review.googlesource.com/77550
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
diff --git a/src/database/sql/driver/driver.go b/src/database/sql/driver/driver.go
index b3f9d9c..b9bf19c 100644
--- a/src/database/sql/driver/driver.go
+++ b/src/database/sql/driver/driver.go
@@ -55,6 +55,17 @@
 	Open(name string) (Conn, error)
 }
 
+// DriverContext enhances the Driver interface by returning a Connector
+// rather then a single Conn.
+// It separates out the name parsing step from actually connecting to the
+// database. It also gives dialers access to the context by using the
+// Connector.
+type DriverContext interface {
+	// OpenConnector must parse the name in the same format that Driver.Open
+	// parses the name parameter.
+	OpenConnector(name string) (Connector, error)
+}
+
 // Connector is an optional interface that drivers can implement.
 // It allows drivers to provide more flexible methods to open
 // database connections without requiring the use of a DSN string.
diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go
index 31e22a7..e795412 100644
--- a/src/database/sql/fakedb_test.go
+++ b/src/database/sql/fakedb_test.go
@@ -71,6 +71,16 @@
 	return fdriver
 }
 
+type fakeDriverCtx struct {
+	fakeDriver
+}
+
+var _ driver.DriverContext = &fakeDriverCtx{}
+
+func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) {
+	return &fakeConnector{name: name}, nil
+}
+
 type fakeDB struct {
 	name string
 
diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
index 30b4ad3..1192eaa 100644
--- a/src/database/sql/sql.go
+++ b/src/database/sql/sql.go
@@ -662,6 +662,14 @@
 		return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
 	}
 
+	if driverCtx, ok := driveri.(driver.DriverContext); ok {
+		connector, err := driverCtx.OpenConnector(dataSourceName)
+		if err != nil {
+			return nil, err
+		}
+		return OpenDB(connector), nil
+	}
+
 	return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
 }
 
diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go
index f7b7d98..8137eff 100644
--- a/src/database/sql/sql_test.go
+++ b/src/database/sql/sql_test.go
@@ -3523,6 +3523,19 @@
 	}
 }
 
+func TestOpenConnector(t *testing.T) {
+	Register("testctx", &fakeDriverCtx{})
+	db, err := Open("testctx", "people")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer db.Close()
+
+	if _, is := db.connector.(*fakeConnector); !is {
+		t.Fatal("not using *fakeConnector")
+	}
+}
+
 type ctxOnlyDriver struct {
 	fakeDriver
 }