From f28c8fba67008ae0b14517979e9b48ac38fa22b6 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 14 Mar 2013 15:01:45 -0700 Subject: [PATCH] database/sql: associate a mutex with each driver interface The database/sql/driver docs make this promise: "Conn is a connection to a database. It is not used concurrently by multiple goroutines." That promises exists as part of database/sql's overall goal of making drivers relatively easy to write. So far this promise has been kept without the use of locks by being careful in the database/sql package, but sometimes too careful. (cf. golang.org/issue/3857) The CL associates a Mutex with each driver.Conn, and with the interface value progeny thereof. (e.g. each driver.Tx, driver.Stmt, driver.Rows, driver.Result, etc) Then whenever those interface values are used, the Locker is locked. This CL should be a no-op (aside from some new Lock/Unlock pairs) and doesn't attempt to fix Issue 3857 or Issue 4459, but should make it much easier in a subsequent CL. Update #3857 R=golang-dev, adg CC=golang-dev https://golang.org/cl/7803043 --- src/pkg/database/sql/convert.go | 10 +- src/pkg/database/sql/sql.go | 313 ++++++++++++++++++++----------- src/pkg/database/sql/sql_test.go | 9 +- 3 files changed, 219 insertions(+), 113 deletions(-) diff --git a/src/pkg/database/sql/convert.go b/src/pkg/database/sql/convert.go index 853a7826c5..a12d564bc3 100644 --- a/src/pkg/database/sql/convert.go +++ b/src/pkg/database/sql/convert.go @@ -19,9 +19,13 @@ var errNilPtr = errors.New("destination pointer is nil") // embedded in descript // driverArgs converts arguments from callers of Stmt.Exec and // Stmt.Query into driver Values. // -// The statement si may be nil, if no statement is available. -func driverArgs(si driver.Stmt, args []interface{}) ([]driver.Value, error) { +// The statement ds may be nil, if no statement is available. +func driverArgs(ds *driverStmt, args []interface{}) ([]driver.Value, error) { dargs := make([]driver.Value, len(args)) + var si driver.Stmt + if ds != nil { + si = ds.si + } cc, ok := si.(driver.ColumnConverter) // Normal path, for a driver.Stmt that is not a ColumnConverter. @@ -60,7 +64,9 @@ func driverArgs(si driver.Stmt, args []interface{}) ([]driver.Value, error) { // column before going across the network to get the // same error. var err error + ds.Lock() dargs[n], err = cc.ColumnConverter(n).ConvertValue(arg) + ds.Unlock() if err != nil { return nil, fmt.Errorf("sql: converting argument #%d's type: %v", n, err) } diff --git a/src/pkg/database/sql/sql.go b/src/pkg/database/sql/sql.go index d351fbc243..a4c410267c 100644 --- a/src/pkg/database/sql/sql.go +++ b/src/pkg/database/sql/sql.go @@ -191,12 +191,35 @@ type DB struct { dsn string mu sync.Mutex // protects following fields - outConn map[driver.Conn]bool // whether the conn is in use - freeConn []driver.Conn + outConn map[*driverConn]bool // whether the conn is in use + freeConn []*driverConn closed bool dep map[finalCloser]depSet - onConnPut map[driver.Conn][]func() // code (with mu held) run when conn is next returned - lastPut map[driver.Conn]string // stacktrace of last conn's put; debug only + onConnPut map[*driverConn][]func() // code (with mu held) run when conn is next returned + lastPut map[*driverConn]string // stacktrace of last conn's put; debug only +} + +// driverConn wraps a driver.Conn with a mutex, to +// be held during all calls into the Conn. (including any calls onto +// interfaces returned via that Conn, such as calls on Tx, Stmt, +// Result, Rows) +type driverConn struct { + sync.Mutex + ci driver.Conn +} + +// driverStmt associates a driver.Stmt with the +// *driverConn from which it came, so the driverConn's lock can be +// held during calls. +type driverStmt struct { + sync.Locker // the *driverConn + si driver.Stmt +} + +func (ds *driverStmt) Close() error { + ds.Lock() + defer ds.Unlock() + return ds.si.Close() } // depSet is a finalCloser's outstanding dependencies @@ -270,9 +293,9 @@ func Open(driverName, dataSourceName string) (*DB, error) { db := &DB{ driver: driveri, dsn: dataSourceName, - outConn: make(map[driver.Conn]bool), - lastPut: make(map[driver.Conn]string), - onConnPut: make(map[driver.Conn][]func()), + outConn: make(map[*driverConn]bool), + lastPut: make(map[*driverConn]string), + onConnPut: make(map[*driverConn][]func()), } return db, nil } @@ -283,11 +306,11 @@ func (db *DB) Ping() error { // TODO(bradfitz): give drivers an optional hook to implement // this in a more efficient or more reliable way, if they // have one. - c, err := db.conn() + dc, err := db.conn() if err != nil { return err } - db.putConn(c, nil) + db.putConn(dc, nil) return nil } @@ -296,8 +319,10 @@ func (db *DB) Close() error { db.mu.Lock() defer db.mu.Unlock() var err error - for _, c := range db.freeConn { - err1 := c.Close() + for _, dc := range db.freeConn { + dc.Lock() + err1 := dc.ci.Close() + dc.Unlock() if err1 != nil { err = err1 } @@ -314,8 +339,8 @@ func (db *DB) maxIdleConns() int { return defaultMaxIdleConns } -// conn returns a newly-opened or cached driver.Conn -func (db *DB) conn() (driver.Conn, error) { +// conn returns a newly-opened or cached *driverConn +func (db *DB) conn() (*driverConn, error) { db.mu.Lock() if db.closed { db.mu.Unlock() @@ -329,13 +354,16 @@ func (db *DB) conn() (driver.Conn, error) { return conn, nil } db.mu.Unlock() - conn, err := db.driver.Open(db.dsn) - if err == nil { - db.mu.Lock() - db.outConn[conn] = true - db.mu.Unlock() + + ci, err := db.driver.Open(db.dsn) + if err != nil { + return nil, err } - return conn, err + dc := &driverConn{ci: ci} + db.mu.Lock() + db.outConn[dc] = true + db.mu.Unlock() + return dc, nil } // connIfFree returns (wanted, true) if wanted is still a valid conn and @@ -343,7 +371,7 @@ func (db *DB) conn() (driver.Conn, error) { // // If wanted is valid but in use, connIfFree returns (wanted, false). // If wanted is invalid, connIfFre returns (nil, false). -func (db *DB) connIfFree(wanted driver.Conn) (conn driver.Conn, ok bool) { +func (db *DB) connIfFree(wanted *driverConn) (conn *driverConn, ok bool) { db.mu.Lock() defer db.mu.Unlock() if db.outConn[wanted] { @@ -362,12 +390,12 @@ func (db *DB) connIfFree(wanted driver.Conn) (conn driver.Conn, ok bool) { } // putConnHook is a hook for testing. -var putConnHook func(*DB, driver.Conn) +var putConnHook func(*DB, *driverConn) // noteUnusedDriverStatement notes that si is no longer used and should // be closed whenever possible (when c is next not in use), unless c is // already closed. -func (db *DB) noteUnusedDriverStatement(c driver.Conn, si driver.Stmt) { +func (db *DB) noteUnusedDriverStatement(c *driverConn, si driver.Stmt) { db.mu.Lock() defer db.mu.Unlock() if db.outConn[c] { @@ -385,24 +413,24 @@ const debugGetPut = false // putConn adds a connection to the db's free pool. // err is optionally the last error that occurred on this connection. -func (db *DB) putConn(c driver.Conn, err error) { +func (db *DB) putConn(dc *driverConn, err error) { db.mu.Lock() - if !db.outConn[c] { + if !db.outConn[dc] { if debugGetPut { - fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", c, stack(), db.lastPut[c]) + fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc]) } panic("sql: connection returned that was never out") } if debugGetPut { - db.lastPut[c] = stack() + db.lastPut[dc] = stack() } - delete(db.outConn, c) + delete(db.outConn, dc) - if fns, ok := db.onConnPut[c]; ok { + if fns, ok := db.onConnPut[dc]; ok { for _, fn := range fns { fn() } - delete(db.onConnPut, c) + delete(db.onConnPut, dc) } if err == driver.ErrBadConn { @@ -411,17 +439,20 @@ func (db *DB) putConn(c driver.Conn, err error) { return } if putConnHook != nil { - putConnHook(db, c) + putConnHook(db, dc) } if n := len(db.freeConn); !db.closed && n < db.maxIdleConns() { - db.freeConn = append(db.freeConn, c) + db.freeConn = append(db.freeConn, dc) db.mu.Unlock() return } // TODO: check to see if we need this Conn for any prepared // statements which are still active? db.mu.Unlock() - c.Close() + + dc.Lock() + dc.ci.Close() + dc.Unlock() } // Prepare creates a prepared statement for later queries or executions. @@ -446,22 +477,24 @@ func (db *DB) prepare(query string) (*Stmt, error) { // to a connection, and to execute this prepared statement // we either need to use this connection (if it's free), else // get a new connection + re-prepare + execute on that one. - ci, err := db.conn() + dc, err := db.conn() if err != nil { return nil, err } - si, err := ci.Prepare(query) + dc.Lock() + si, err := dc.ci.Prepare(query) + dc.Unlock() if err != nil { - db.putConn(ci, err) + db.putConn(dc, err) return nil, err } stmt := &Stmt{ db: db, query: query, - css: []connStmt{{ci, si}}, + css: []connStmt{{dc, si}}, } db.addDep(stmt, stmt) - db.putConn(ci, nil) + db.putConn(dc, nil) return stmt, nil } @@ -480,35 +513,39 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) { } func (db *DB) exec(query string, args []interface{}) (res Result, err error) { - ci, err := db.conn() + dc, err := db.conn() if err != nil { return nil, err } defer func() { - db.putConn(ci, err) + db.putConn(dc, err) }() - if execer, ok := ci.(driver.Execer); ok { + if execer, ok := dc.ci.(driver.Execer); ok { dargs, err := driverArgs(nil, args) if err != nil { return nil, err } + dc.Lock() resi, err := execer.Exec(query, dargs) + dc.Unlock() if err != driver.ErrSkip { if err != nil { return nil, err } - return result{resi}, nil + return driverResult{dc, resi}, nil } } - sti, err := ci.Prepare(query) + dc.Lock() + si, err := dc.ci.Prepare(query) + dc.Unlock() if err != nil { return nil, err } - defer sti.Close() + defer withLock(dc, func() { si.Close() }) - return resultFromStatement(sti, args...) + return resultFromStatement(driverStmt{dc, si}, args...) } // Query executes a query that returns rows, typically a SELECT. @@ -538,14 +575,16 @@ func (db *DB) query(query string, args []interface{}) (*Rows, error) { // queryConn executes a query on the given connection. // The connection gets released by the releaseConn function. -func (db *DB) queryConn(ci driver.Conn, releaseConn func(error), query string, args []interface{}) (*Rows, error) { - if queryer, ok := ci.(driver.Queryer); ok { +func (db *DB) queryConn(dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) { + if queryer, ok := dc.ci.(driver.Queryer); ok { dargs, err := driverArgs(nil, args) if err != nil { releaseConn(err) return nil, err } + dc.Lock() rowsi, err := queryer.Query(query, dargs) + dc.Unlock() if err != driver.ErrSkip { if err != nil { releaseConn(err) @@ -555,7 +594,7 @@ func (db *DB) queryConn(ci driver.Conn, releaseConn func(error), query string, a // with releaseConn. rows := &Rows{ db: db, - ci: ci, + dc: dc, releaseConn: releaseConn, rowsi: rowsi, } @@ -563,16 +602,21 @@ func (db *DB) queryConn(ci driver.Conn, releaseConn func(error), query string, a } } - sti, err := ci.Prepare(query) + dc.Lock() + si, err := dc.ci.Prepare(query) + dc.Unlock() if err != nil { releaseConn(err) return nil, err } - rowsi, err := rowsiFromStatement(sti, args...) + ds := driverStmt{dc, si} + rowsi, err := rowsiFromStatement(ds, args...) if err != nil { releaseConn(err) - sti.Close() + dc.Lock() + si.Close() + dc.Unlock() return nil, err } @@ -580,10 +624,10 @@ func (db *DB) queryConn(ci driver.Conn, releaseConn func(error), query string, a // with releaseConn. rows := &Rows{ db: db, - ci: ci, + dc: dc, releaseConn: releaseConn, rowsi: rowsi, - closeStmt: sti, + closeStmt: si, } return rows, nil } @@ -611,18 +655,20 @@ func (db *DB) Begin() (*Tx, error) { } func (db *DB) begin() (tx *Tx, err error) { - ci, err := db.conn() + dc, err := db.conn() if err != nil { return nil, err } - txi, err := ci.Begin() + dc.Lock() + txi, err := dc.ci.Begin() + dc.Unlock() if err != nil { - db.putConn(ci, err) + db.putConn(dc, err) return nil, err } return &Tx{ db: db, - ci: ci, + dc: dc, txi: txi, }, nil } @@ -641,13 +687,15 @@ func (db *DB) Driver() driver.Driver { type Tx struct { db *DB - // ci is owned exclusively until Commit or Rollback, at which point + // dc is owned exclusively until Commit or Rollback, at which point // it's returned with putConn. - ci driver.Conn + // TODO(bradfitz): golang.org/issue/3857 + dc *driverConn txi driver.Tx // cimu is held while somebody is using ci (between grabConn // and releaseConn) + // TODO(bradfitz): golang.org/issue/3857 cimu sync.Mutex // done transitions from false to true exactly once, on Commit @@ -663,17 +711,17 @@ func (tx *Tx) close() { panic("double close") // internal error } tx.done = true - tx.db.putConn(tx.ci, nil) - tx.ci = nil + tx.db.putConn(tx.dc, nil) + tx.dc = nil tx.txi = nil } -func (tx *Tx) grabConn() (driver.Conn, error) { +func (tx *Tx) grabConn() (*driverConn, error) { if tx.done { return nil, ErrTxDone } tx.cimu.Lock() - return tx.ci, nil + return tx.dc, nil } func (tx *Tx) releaseConn() { @@ -686,6 +734,8 @@ func (tx *Tx) Commit() error { return ErrTxDone } defer tx.close() + tx.dc.Lock() + defer tx.dc.Unlock() return tx.txi.Commit() } @@ -695,6 +745,8 @@ func (tx *Tx) Rollback() error { return ErrTxDone } defer tx.close() + tx.dc.Lock() + defer tx.dc.Unlock() return tx.txi.Rollback() } @@ -718,21 +770,26 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) { // Perhaps just looking at the reference count (by noting // Stmt.Close) would be enough. We might also want a finalizer // on Stmt to drop the reference count. - ci, err := tx.grabConn() + dc, err := tx.grabConn() if err != nil { return nil, err } defer tx.releaseConn() - si, err := ci.Prepare(query) + dc.Lock() + si, err := dc.ci.Prepare(query) + dc.Unlock() if err != nil { return nil, err } stmt := &Stmt{ - db: tx.db, - tx: tx, - txsi: si, + db: tx.db, + tx: tx, + txsi: &driverStmt{ + Locker: dc, + si: si, + }, query: query, } return stmt, nil @@ -756,16 +813,21 @@ func (tx *Tx) Stmt(stmt *Stmt) *Stmt { if tx.db != stmt.db { return &Stmt{stickyErr: errors.New("sql: Tx.Stmt: statement from different database used")} } - ci, err := tx.grabConn() + dc, err := tx.grabConn() if err != nil { return &Stmt{stickyErr: err} } defer tx.releaseConn() - si, err := ci.Prepare(stmt.query) + dc.Lock() + si, err := dc.ci.Prepare(stmt.query) + dc.Unlock() return &Stmt{ - db: tx.db, - tx: tx, - txsi: si, + db: tx.db, + tx: tx, + txsi: &driverStmt{ + Locker: dc, + si: si, + }, query: stmt.query, stickyErr: err, } @@ -774,33 +836,37 @@ func (tx *Tx) Stmt(stmt *Stmt) *Stmt { // Exec executes a query that doesn't return rows. // For example: an INSERT and UPDATE. func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) { - ci, err := tx.grabConn() + dc, err := tx.grabConn() if err != nil { return nil, err } defer tx.releaseConn() - if execer, ok := ci.(driver.Execer); ok { + if execer, ok := dc.ci.(driver.Execer); ok { dargs, err := driverArgs(nil, args) if err != nil { return nil, err } + dc.Lock() resi, err := execer.Exec(query, dargs) + dc.Unlock() if err == nil { - return result{resi}, nil + return driverResult{dc, resi}, nil } if err != driver.ErrSkip { return nil, err } } - sti, err := ci.Prepare(query) + dc.Lock() + si, err := dc.ci.Prepare(query) + dc.Unlock() if err != nil { return nil, err } - defer sti.Close() + defer withLock(dc, func() { si.Close() }) - return resultFromStatement(sti, args...) + return resultFromStatement(driverStmt{dc, si}, args...) } // Query executes a query that returns rows, typically a SELECT. @@ -825,7 +891,7 @@ func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { // connStmt is a prepared statement on a particular connection. type connStmt struct { - ci driver.Conn + dc *driverConn si driver.Stmt } @@ -840,7 +906,7 @@ type Stmt struct { // If in a transaction, else both nil: tx *Tx - txsi driver.Stmt + txsi *driverStmt mu sync.Mutex // protects the rest of the fields closed bool @@ -857,39 +923,45 @@ type Stmt struct { func (s *Stmt) Exec(args ...interface{}) (Result, error) { s.closemu.RLock() defer s.closemu.RUnlock() - _, releaseConn, si, err := s.connStmt() + dc, releaseConn, si, err := s.connStmt() if err != nil { return nil, err } defer releaseConn(nil) - return resultFromStatement(si, args...) + return resultFromStatement(driverStmt{dc, si}, args...) } -func resultFromStatement(si driver.Stmt, args ...interface{}) (Result, error) { +func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) { + ds.Lock() + want := ds.si.NumInput() + ds.Unlock() + // -1 means the driver doesn't know how to count the number of // placeholders, so we won't sanity check input here and instead let the // driver deal with errors. - if want := si.NumInput(); want != -1 && len(args) != want { + if want != -1 && len(args) != want { return nil, fmt.Errorf("sql: expected %d arguments, got %d", want, len(args)) } - dargs, err := driverArgs(si, args) + dargs, err := driverArgs(&ds, args) if err != nil { return nil, err } - resi, err := si.Exec(dargs) + ds.Lock() + resi, err := ds.si.Exec(dargs) + ds.Unlock() if err != nil { return nil, err } - return result{resi}, nil + return driverResult{ds.Locker, resi}, nil } // connStmt returns a free driver connection on which to execute the // statement, a function to call to release the connection, and a // statement bound to that connection. -func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.Stmt, err error) { +func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.Stmt, err error) { if err = s.stickyErr; err != nil { return } @@ -909,7 +981,7 @@ func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.St return } releaseConn = func(error) { s.tx.releaseConn() } - return ci, releaseConn, s.txsi, nil + return ci, releaseConn, s.txsi.si, nil } var cs connStmt @@ -917,7 +989,7 @@ func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.St for _, v := range s.css { // TODO(bradfitz): lazily clean up entries in this // list with dead conns while enumerating - if _, match = s.db.connIfFree(v.ci); match { + if _, match = s.db.connIfFree(v.dc); match { cs = v break } @@ -928,11 +1000,13 @@ func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.St // TODO(bradfitz): or wait for one? make configurable later? if !match { for i := 0; ; i++ { - ci, err := s.db.conn() + dc, err := s.db.conn() if err != nil { return nil, nil, nil, err } - si, err := ci.Prepare(s.query) + dc.Lock() + si, err := dc.ci.Prepare(s.query) + dc.Unlock() if err == driver.ErrBadConn && i < 10 { continue } @@ -940,14 +1014,14 @@ func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.St return nil, nil, nil, err } s.mu.Lock() - cs = connStmt{ci, si} + cs = connStmt{dc, si} s.css = append(s.css, cs) s.mu.Unlock() break } } - conn := cs.ci + conn := cs.dc releaseConn = func(err error) { s.db.putConn(conn, err) } return conn, releaseConn, cs.si, nil } @@ -958,12 +1032,13 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { s.closemu.RLock() defer s.closemu.RUnlock() - ci, releaseConn, si, err := s.connStmt() + dc, releaseConn, si, err := s.connStmt() if err != nil { return nil, err } - rowsi, err := rowsiFromStatement(si, args...) + ds := driverStmt{dc, si} + rowsi, err := rowsiFromStatement(ds, args...) if err != nil { releaseConn(err) return nil, err @@ -973,7 +1048,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { // with releaseConn. rows := &Rows{ db: s.db, - ci: ci, + dc: dc, rowsi: rowsi, // releaseConn set below } @@ -985,20 +1060,26 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { return rows, nil } -func rowsiFromStatement(si driver.Stmt, args ...interface{}) (driver.Rows, error) { +func rowsiFromStatement(ds driverStmt, args ...interface{}) (driver.Rows, error) { + ds.Lock() + want := ds.si.NumInput() + ds.Unlock() + // -1 means the driver doesn't know how to count the number of // placeholders, so we won't sanity check input here and instead let the // driver deal with errors. - if want := si.NumInput(); want != -1 && len(args) != want { - return nil, fmt.Errorf("sql: statement expects %d inputs; got %d", si.NumInput(), len(args)) + if want != -1 && len(args) != want { + return nil, fmt.Errorf("sql: statement expects %d inputs; got %d", want, len(args)) } - dargs, err := driverArgs(si, args) + dargs, err := driverArgs(&ds, args) if err != nil { return nil, err } - rowsi, err := si.Query(dargs) + ds.Lock() + rowsi, err := ds.si.Query(dargs) + ds.Unlock() if err != nil { return nil, err } @@ -1049,7 +1130,7 @@ func (s *Stmt) Close() error { func (s *Stmt) finalClose() error { for _, v := range s.css { - s.db.noteUnusedDriverStatement(v.ci, v.si) + s.db.noteUnusedDriverStatement(v.dc, v.si) } s.css = nil return nil @@ -1070,7 +1151,7 @@ func (s *Stmt) finalClose() error { // ... type Rows struct { db *DB - ci driver.Conn // owned; must call releaseConn when closed to release + dc *driverConn // owned; must call releaseConn when closed to release releaseConn func(error) rowsi driver.Rows @@ -1243,11 +1324,31 @@ type Result interface { RowsAffected() (int64, error) } -type result struct { - driver.Result +type driverResult struct { + sync.Locker // the *driverConn + resi driver.Result +} + +func (dr driverResult) LastInsertId() (int64, error) { + dr.Lock() + defer dr.Unlock() + return dr.resi.LastInsertId() +} + +func (dr driverResult) RowsAffected() (int64, error) { + dr.Lock() + defer dr.Unlock() + return dr.resi.RowsAffected() } func stack() string { var buf [1024]byte return string(buf[:runtime.Stack(buf[:], false)]) } + +// withLock runs while holding lk. +func withLock(lk sync.Locker, fn func()) { + lk.Lock() + fn() + lk.Unlock() +} diff --git a/src/pkg/database/sql/sql_test.go b/src/pkg/database/sql/sql_test.go index f5c3f1ed65..57300bc4d9 100644 --- a/src/pkg/database/sql/sql_test.go +++ b/src/pkg/database/sql/sql_test.go @@ -5,7 +5,6 @@ package sql import ( - "database/sql/driver" "fmt" "reflect" "strings" @@ -16,10 +15,10 @@ import ( func init() { type dbConn struct { db *DB - c driver.Conn + c *driverConn } freedFrom := make(map[dbConn]string) - putConnHook = func(db *DB, c driver.Conn) { + putConnHook = func(db *DB, c *driverConn) { for _, oc := range db.freeConn { if oc == c { // print before panic, as panic may get lost due to conflicting panic @@ -78,7 +77,7 @@ func numPrepares(t *testing.T, db *DB) int { if n := len(db.freeConn); n != 1 { t.Fatalf("free conns = %d; want 1", n) } - return db.freeConn[0].(*fakeConn).numPrepare + return db.freeConn[0].ci.(*fakeConn).numPrepare } func TestQuery(t *testing.T) { @@ -576,7 +575,7 @@ func TestQueryRowClosingStmt(t *testing.T) { if len(db.freeConn) != 1 { t.Fatalf("expected 1 free conn") } - fakeConn := db.freeConn[0].(*fakeConn) + fakeConn := db.freeConn[0].ci.(*fakeConn) if made, closed := fakeConn.stmtsMade, fakeConn.stmtsClosed; made != closed { t.Errorf("statement close mismatch: made %d, closed %d", made, closed) } -- 2.50.0