mirror of
https://github.com/golang/go
synced 2024-11-20 03:54:40 -07:00
database/sql: add OpenDB to directly create a *DB without a DSN.
The current Open method limits the ability for driver maintainers to expose options for their drivers by forcing all the configuration to pass through the DSN in order to create a *DB. This CL allows driver maintainers to write their own initialization functions that return a *DB making configuration of the underlying drivers easier. Fixes #20268 Change-Id: Ib10b794f36a201bbb92c23999c8351815d38eedb Reviewed-on: https://go-review.googlesource.com/53430 Reviewed-by: Daniel Theophanes <kardianos@gmail.com> Run-TryBot: Daniel Theophanes <kardianos@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
6936671ed1
commit
e6358c798b
@ -55,6 +55,29 @@ type Driver interface {
|
|||||||
Open(name string) (Conn, error)
|
Open(name string) (Conn, 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.
|
||||||
|
type Connector interface {
|
||||||
|
// Connect returns a connection to the database.
|
||||||
|
// Connect may return a cached connection (one previously
|
||||||
|
// closed), but doing so is unnecessary; the sql package
|
||||||
|
// maintains a pool of idle connections for efficient re-use.
|
||||||
|
//
|
||||||
|
// The provided context.Context is for dialing purposes only
|
||||||
|
// (see net.DialContext) and should not be stored or used for
|
||||||
|
// other purposes.
|
||||||
|
//
|
||||||
|
// The returned connection is only used by one goroutine at a
|
||||||
|
// time.
|
||||||
|
Connect(context.Context) (Conn, error)
|
||||||
|
|
||||||
|
// Driver returns the underlying Driver of the Connector,
|
||||||
|
// mainly to maintain compatibility with the Driver method
|
||||||
|
// on sql.DB.
|
||||||
|
Driver() Driver
|
||||||
|
}
|
||||||
|
|
||||||
// ErrSkip may be returned by some optional interfaces' methods to
|
// ErrSkip may be returned by some optional interfaces' methods to
|
||||||
// indicate at runtime that the fast path is unavailable and the sql
|
// indicate at runtime that the fast path is unavailable and the sql
|
||||||
// package should continue as if the optional interface was not
|
// package should continue as if the optional interface was not
|
||||||
|
@ -317,8 +317,7 @@ var ErrNoRows = errors.New("sql: no rows in result set")
|
|||||||
// connection is returned to DB's idle connection pool. The pool size
|
// connection is returned to DB's idle connection pool. The pool size
|
||||||
// can be controlled with SetMaxIdleConns.
|
// can be controlled with SetMaxIdleConns.
|
||||||
type DB struct {
|
type DB struct {
|
||||||
driver driver.Driver
|
connector driver.Connector
|
||||||
dsn string
|
|
||||||
// numClosed is an atomic counter which represents a total number of
|
// numClosed is an atomic counter which represents a total number of
|
||||||
// closed connections. Stmt.openStmt checks it before cleaning closed
|
// closed connections. Stmt.openStmt checks it before cleaning closed
|
||||||
// connections in Stmt.css.
|
// connections in Stmt.css.
|
||||||
@ -575,6 +574,48 @@ func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error {
|
|||||||
// to block until the connectionOpener can satisfy the backlog of requests.
|
// to block until the connectionOpener can satisfy the backlog of requests.
|
||||||
var connectionRequestQueueSize = 1000000
|
var connectionRequestQueueSize = 1000000
|
||||||
|
|
||||||
|
type dsnConnector struct {
|
||||||
|
dsn string
|
||||||
|
driver driver.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) {
|
||||||
|
return t.driver.Open(t.dsn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t dsnConnector) Driver() driver.Driver {
|
||||||
|
return t.driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDB opens a database using a Connector, allowing drivers to
|
||||||
|
// bypass a string based data source name.
|
||||||
|
//
|
||||||
|
// Most users will open a database via a driver-specific connection
|
||||||
|
// helper function that returns a *DB. No database drivers are included
|
||||||
|
// in the Go standard library. See https://golang.org/s/sqldrivers for
|
||||||
|
// a list of third-party drivers.
|
||||||
|
//
|
||||||
|
// OpenDB may just validate its arguments without creating a connection
|
||||||
|
// to the database. To verify that the data source name is valid, call
|
||||||
|
// Ping.
|
||||||
|
//
|
||||||
|
// The returned DB is safe for concurrent use by multiple goroutines
|
||||||
|
// and maintains its own pool of idle connections. Thus, the OpenDB
|
||||||
|
// function should be called just once. It is rarely necessary to
|
||||||
|
// close a DB.
|
||||||
|
func OpenDB(c driver.Connector) *DB {
|
||||||
|
db := &DB{
|
||||||
|
connector: c,
|
||||||
|
openerCh: make(chan struct{}, connectionRequestQueueSize),
|
||||||
|
lastPut: make(map[*driverConn]string),
|
||||||
|
connRequests: make(map[uint64]chan connRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
go db.connectionOpener()
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
// Open opens a database specified by its database driver name and a
|
// Open opens a database specified by its database driver name and a
|
||||||
// driver-specific data source name, usually consisting of at least a
|
// driver-specific data source name, usually consisting of at least a
|
||||||
// database name and connection information.
|
// database name and connection information.
|
||||||
@ -599,15 +640,8 @@ func Open(driverName, dataSourceName string) (*DB, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
|
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
|
||||||
}
|
}
|
||||||
db := &DB{
|
|
||||||
driver: driveri,
|
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
|
||||||
dsn: dataSourceName,
|
|
||||||
openerCh: make(chan struct{}, connectionRequestQueueSize),
|
|
||||||
lastPut: make(map[*driverConn]string),
|
|
||||||
connRequests: make(map[uint64]chan connRequest),
|
|
||||||
}
|
|
||||||
go db.connectionOpener()
|
|
||||||
return db, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) pingDC(ctx context.Context, dc *driverConn, release func(error)) error {
|
func (db *DB) pingDC(ctx context.Context, dc *driverConn, release func(error)) error {
|
||||||
@ -878,7 +912,7 @@ func (db *DB) openNewConnection() {
|
|||||||
// maybeOpenNewConnctions has already executed db.numOpen++ before it sent
|
// maybeOpenNewConnctions has already executed db.numOpen++ before it sent
|
||||||
// on db.openerCh. This function must execute db.numOpen-- if the
|
// on db.openerCh. This function must execute db.numOpen-- if the
|
||||||
// connection fails or is closed before returning.
|
// connection fails or is closed before returning.
|
||||||
ci, err := db.driver.Open(db.dsn)
|
ci, err := db.connector.Connect(context.Background())
|
||||||
db.mu.Lock()
|
db.mu.Lock()
|
||||||
defer db.mu.Unlock()
|
defer db.mu.Unlock()
|
||||||
if db.closed {
|
if db.closed {
|
||||||
@ -996,7 +1030,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
|
|||||||
|
|
||||||
db.numOpen++ // optimistically
|
db.numOpen++ // optimistically
|
||||||
db.mu.Unlock()
|
db.mu.Unlock()
|
||||||
ci, err := db.driver.Open(db.dsn)
|
ci, err := db.connector.Connect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
db.mu.Lock()
|
db.mu.Lock()
|
||||||
db.numOpen-- // correct for earlier optimism
|
db.numOpen-- // correct for earlier optimism
|
||||||
@ -1454,7 +1488,7 @@ func (db *DB) beginDC(ctx context.Context, dc *driverConn, release func(error),
|
|||||||
|
|
||||||
// Driver returns the database's underlying driver.
|
// Driver returns the database's underlying driver.
|
||||||
func (db *DB) Driver() driver.Driver {
|
func (db *DB) Driver() driver.Driver {
|
||||||
return db.driver
|
return db.connector.Driver()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrConnDone is returned by any operation that is performed on a connection
|
// ErrConnDone is returned by any operation that is performed on a connection
|
||||||
|
@ -81,6 +81,13 @@ func newTestDB(t testing.TB, name string) *DB {
|
|||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenDB(t *testing.T) {
|
||||||
|
db := OpenDB(dsnConnector{dsn: fakeDBName, driver: fdriver})
|
||||||
|
if db.Driver() != fdriver {
|
||||||
|
t.Fatalf("OpenDB should return the driver of the Connector")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDriverPanic(t *testing.T) {
|
func TestDriverPanic(t *testing.T) {
|
||||||
// Test that if driver panics, database/sql does not deadlock.
|
// Test that if driver panics, database/sql does not deadlock.
|
||||||
db, err := Open("test", fakeDBName)
|
db, err := Open("test", fakeDBName)
|
||||||
@ -1672,7 +1679,7 @@ func TestIssue4902(t *testing.T) {
|
|||||||
db := newTestDB(t, "people")
|
db := newTestDB(t, "people")
|
||||||
defer closeDB(t, db)
|
defer closeDB(t, db)
|
||||||
|
|
||||||
driver := db.driver.(*fakeDriver)
|
driver := db.Driver().(*fakeDriver)
|
||||||
opens0 := driver.openCount
|
opens0 := driver.openCount
|
||||||
|
|
||||||
var stmt *Stmt
|
var stmt *Stmt
|
||||||
@ -1765,7 +1772,7 @@ func TestMaxOpenConns(t *testing.T) {
|
|||||||
db := newTestDB(t, "magicquery")
|
db := newTestDB(t, "magicquery")
|
||||||
defer closeDB(t, db)
|
defer closeDB(t, db)
|
||||||
|
|
||||||
driver := db.driver.(*fakeDriver)
|
driver := db.Driver().(*fakeDriver)
|
||||||
|
|
||||||
// Force the number of open connections to 0 so we can get an accurate
|
// Force the number of open connections to 0 so we can get an accurate
|
||||||
// count for the test
|
// count for the test
|
||||||
@ -2057,7 +2064,7 @@ func TestConnMaxLifetime(t *testing.T) {
|
|||||||
db := newTestDB(t, "magicquery")
|
db := newTestDB(t, "magicquery")
|
||||||
defer closeDB(t, db)
|
defer closeDB(t, db)
|
||||||
|
|
||||||
driver := db.driver.(*fakeDriver)
|
driver := db.Driver().(*fakeDriver)
|
||||||
|
|
||||||
// Force the number of open connections to 0 so we can get an accurate
|
// Force the number of open connections to 0 so we can get an accurate
|
||||||
// count for the test
|
// count for the test
|
||||||
@ -2146,7 +2153,7 @@ func TestStmtCloseDeps(t *testing.T) {
|
|||||||
db := newTestDB(t, "magicquery")
|
db := newTestDB(t, "magicquery")
|
||||||
defer closeDB(t, db)
|
defer closeDB(t, db)
|
||||||
|
|
||||||
driver := db.driver.(*fakeDriver)
|
driver := db.Driver().(*fakeDriver)
|
||||||
|
|
||||||
driver.mu.Lock()
|
driver.mu.Lock()
|
||||||
opens0 := driver.openCount
|
opens0 := driver.openCount
|
||||||
@ -3071,7 +3078,7 @@ func TestIssue6081(t *testing.T) {
|
|||||||
db := newTestDB(t, "people")
|
db := newTestDB(t, "people")
|
||||||
defer closeDB(t, db)
|
defer closeDB(t, db)
|
||||||
|
|
||||||
drv := db.driver.(*fakeDriver)
|
drv := db.Driver().(*fakeDriver)
|
||||||
drv.mu.Lock()
|
drv.mu.Lock()
|
||||||
opens0 := drv.openCount
|
opens0 := drv.openCount
|
||||||
closes0 := drv.closeCount
|
closes0 := drv.closeCount
|
||||||
@ -3326,7 +3333,7 @@ func TestConnectionLeak(t *testing.T) {
|
|||||||
// Now we have defaultMaxIdleConns busy connections. Open
|
// Now we have defaultMaxIdleConns busy connections. Open
|
||||||
// a new one, but wait until the busy connections are released
|
// a new one, but wait until the busy connections are released
|
||||||
// before returning control to DB.
|
// before returning control to DB.
|
||||||
drv := db.driver.(*fakeDriver)
|
drv := db.Driver().(*fakeDriver)
|
||||||
drv.waitCh = make(chan struct{}, 1)
|
drv.waitCh = make(chan struct{}, 1)
|
||||||
drv.waitingCh = make(chan struct{}, 1)
|
drv.waitingCh = make(chan struct{}, 1)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
Loading…
Reference in New Issue
Block a user