summaryrefslogtreecommitdiff
path: root/src/pkg/database/sql
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/database/sql')
-rw-r--r--src/pkg/database/sql/convert.go50
-rw-r--r--src/pkg/database/sql/convert_test.go56
-rw-r--r--src/pkg/database/sql/driver/driver.go2
-rw-r--r--src/pkg/database/sql/example_test.go1
-rw-r--r--src/pkg/database/sql/fakedb_test.go32
-rw-r--r--src/pkg/database/sql/sql.go166
-rw-r--r--src/pkg/database/sql/sql_test.go137
7 files changed, 364 insertions, 80 deletions
diff --git a/src/pkg/database/sql/convert.go b/src/pkg/database/sql/convert.go
index c04adde1f..c0b38a249 100644
--- a/src/pkg/database/sql/convert.go
+++ b/src/pkg/database/sql/convert.go
@@ -160,27 +160,19 @@ func convertAssign(dest, src interface{}) error {
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
- *d = fmt.Sprintf("%v", src)
+ *d = asString(src)
return nil
}
case *[]byte:
sv = reflect.ValueOf(src)
- switch sv.Kind() {
- case reflect.Bool,
- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
- reflect.Float32, reflect.Float64:
- *d = []byte(fmt.Sprintf("%v", src))
+ if b, ok := asBytes(nil, sv); ok {
+ *d = b
return nil
}
case *RawBytes:
sv = reflect.ValueOf(src)
- switch sv.Kind() {
- case reflect.Bool,
- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
- reflect.Float32, reflect.Float64:
- *d = RawBytes(fmt.Sprintf("%v", src))
+ if b, ok := asBytes([]byte(*d)[:0], sv); ok {
+ *d = RawBytes(b)
return nil
}
case *bool:
@@ -271,5 +263,37 @@ func asString(src interface{}) string {
case []byte:
return string(v)
}
+ rv := reflect.ValueOf(src)
+ switch rv.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return strconv.FormatInt(rv.Int(), 10)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return strconv.FormatUint(rv.Uint(), 10)
+ case reflect.Float64:
+ return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
+ case reflect.Float32:
+ return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
+ case reflect.Bool:
+ return strconv.FormatBool(rv.Bool())
+ }
return fmt.Sprintf("%v", src)
}
+
+func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) {
+ switch rv.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return strconv.AppendInt(buf, rv.Int(), 10), true
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return strconv.AppendUint(buf, rv.Uint(), 10), true
+ case reflect.Float32:
+ return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true
+ case reflect.Float64:
+ return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true
+ case reflect.Bool:
+ return strconv.AppendBool(buf, rv.Bool()), true
+ case reflect.String:
+ s := rv.String()
+ return append(buf, s...), true
+ }
+ return
+}
diff --git a/src/pkg/database/sql/convert_test.go b/src/pkg/database/sql/convert_test.go
index a39c2c54f..6e2483012 100644
--- a/src/pkg/database/sql/convert_test.go
+++ b/src/pkg/database/sql/convert_test.go
@@ -8,6 +8,7 @@ import (
"database/sql/driver"
"fmt"
"reflect"
+ "runtime"
"testing"
"time"
)
@@ -279,3 +280,58 @@ func TestValueConverters(t *testing.T) {
}
}
}
+
+// Tests that assigning to RawBytes doesn't allocate (and also works).
+func TestRawBytesAllocs(t *testing.T) {
+ buf := make(RawBytes, 10)
+ test := func(name string, in interface{}, want string) {
+ if err := convertAssign(&buf, in); err != nil {
+ t.Fatalf("%s: convertAssign = %v", name, err)
+ }
+ match := len(buf) == len(want)
+ if match {
+ for i, b := range buf {
+ if want[i] != b {
+ match = false
+ break
+ }
+ }
+ }
+ if !match {
+ t.Fatalf("%s: got %q (len %d); want %q (len %d)", name, buf, len(buf), want, len(want))
+ }
+ }
+ n := testing.AllocsPerRun(100, func() {
+ test("uint64", uint64(12345678), "12345678")
+ test("uint32", uint32(1234), "1234")
+ test("uint16", uint16(12), "12")
+ test("uint8", uint8(1), "1")
+ test("uint", uint(123), "123")
+ test("int", int(123), "123")
+ test("int8", int8(1), "1")
+ test("int16", int16(12), "12")
+ test("int32", int32(1234), "1234")
+ test("int64", int64(12345678), "12345678")
+ test("float32", float32(1.5), "1.5")
+ test("float64", float64(64), "64")
+ test("bool", false, "false")
+ })
+
+ // The numbers below are only valid for 64-bit interface word sizes,
+ // and gc. With 32-bit words there are more convT2E allocs, and
+ // with gccgo, only pointers currently go in interface data.
+ // So only care on amd64 gc for now.
+ measureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc"
+
+ if n > 0.5 && measureAllocs {
+ t.Fatalf("allocs = %v; want 0", n)
+ }
+
+ // This one involves a convT2E allocation, string -> interface{}
+ n = testing.AllocsPerRun(100, func() {
+ test("string", "foo", "foo")
+ })
+ if n > 1.5 && measureAllocs {
+ t.Fatalf("allocs = %v; want max 1", n)
+ }
+}
diff --git a/src/pkg/database/sql/driver/driver.go b/src/pkg/database/sql/driver/driver.go
index 0828e63c6..eca25f29a 100644
--- a/src/pkg/database/sql/driver/driver.go
+++ b/src/pkg/database/sql/driver/driver.go
@@ -134,7 +134,7 @@ type Stmt interface {
// as an INSERT or UPDATE.
Exec(args []Value) (Result, error)
- // Exec executes a query that may return rows, such as a
+ // Query executes a query that may return rows, such as a
// SELECT.
Query(args []Value) (Rows, error)
}
diff --git a/src/pkg/database/sql/example_test.go b/src/pkg/database/sql/example_test.go
index d47eed50c..dcb74e069 100644
--- a/src/pkg/database/sql/example_test.go
+++ b/src/pkg/database/sql/example_test.go
@@ -18,6 +18,7 @@ func ExampleDB_Query() {
if err != nil {
log.Fatal(err)
}
+ defer rows.Close()
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
diff --git a/src/pkg/database/sql/fakedb_test.go b/src/pkg/database/sql/fakedb_test.go
index a8adfdd94..c7db0dd77 100644
--- a/src/pkg/database/sql/fakedb_test.go
+++ b/src/pkg/database/sql/fakedb_test.go
@@ -23,7 +23,7 @@ var _ = log.Printf
// interface, just for testing.
//
// It speaks a query language that's semantically similar to but
-// syntantically different and simpler than SQL. The syntax is as
+// syntactically different and simpler than SQL. The syntax is as
// follows:
//
// WIPE
@@ -433,11 +433,19 @@ func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (driver.Stmt, e
return stmt, nil
}
+// hook to simulate broken connections
+var hookPrepareBadConn func() bool
+
func (c *fakeConn) Prepare(query string) (driver.Stmt, error) {
c.numPrepare++
if c.db == nil {
panic("nil c.db; conn = " + fmt.Sprintf("%#v", c))
}
+
+ if hookPrepareBadConn != nil && hookPrepareBadConn() {
+ return nil, driver.ErrBadConn
+ }
+
parts := strings.Split(query, "|")
if len(parts) < 1 {
return nil, errf("empty query")
@@ -489,10 +497,18 @@ func (s *fakeStmt) Close() error {
var errClosed = errors.New("fakedb: statement has been closed")
+// hook to simulate broken connections
+var hookExecBadConn func() bool
+
func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) {
if s.closed {
return nil, errClosed
}
+
+ if hookExecBadConn != nil && hookExecBadConn() {
+ return nil, driver.ErrBadConn
+ }
+
err := checkSubsetTypes(args)
if err != nil {
return nil, err
@@ -565,10 +581,18 @@ func (s *fakeStmt) execInsert(args []driver.Value, doInsert bool) (driver.Result
return driver.RowsAffected(1), nil
}
+// hook to simulate broken connections
+var hookQueryBadConn func() bool
+
func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) {
if s.closed {
return nil, errClosed
}
+
+ if hookQueryBadConn != nil && hookQueryBadConn() {
+ return nil, driver.ErrBadConn
+ }
+
err := checkSubsetTypes(args)
if err != nil {
return nil, err
@@ -686,7 +710,13 @@ func (rc *rowsCursor) Columns() []string {
return rc.cols
}
+var rowsCursorNextHook func(dest []driver.Value) error
+
func (rc *rowsCursor) Next(dest []driver.Value) error {
+ if rowsCursorNextHook != nil {
+ return rowsCursorNextHook(dest)
+ }
+
if rc.closed {
return errors.New("fakedb: cursor is closed")
}
diff --git a/src/pkg/database/sql/sql.go b/src/pkg/database/sql/sql.go
index 84a096513..765b80c60 100644
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -181,7 +181,8 @@ type Scanner interface {
// defers this error until a Scan.
var ErrNoRows = errors.New("sql: no rows in result set")
-// DB is a database handle. It's safe for concurrent use by multiple
+// DB is a database handle representing a pool of zero or more
+// underlying connections. It's safe for concurrent use by multiple
// goroutines.
//
// The sql package creates and frees connections automatically; it
@@ -256,7 +257,7 @@ func (dc *driverConn) prepareLocked(query string) (driver.Stmt, error) {
// stmt closes if the conn is about to close anyway? For now
// do the safe thing, in case stmts need to be closed.
//
- // TODO(bradfitz): after Go 1.1, closing driver.Stmts
+ // TODO(bradfitz): after Go 1.2, closing driver.Stmts
// should be moved to driverStmt, using unique
// *driverStmts everywhere (including from
// *Stmt.connStmt, instead of returning a
@@ -405,7 +406,7 @@ func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error {
// This value should be larger than the maximum typical value
// used for db.maxOpen. If maxOpen is significantly larger than
// connectionRequestQueueSize then it is possible for ALL calls into the *DB
-// to block until the connectionOpener can satify the backlog of requests.
+// to block until the connectionOpener can satisfy the backlog of requests.
var connectionRequestQueueSize = 1000000
// Open opens a database specified by its database driver name and a
@@ -420,6 +421,11 @@ var connectionRequestQueueSize = 1000000
// Open 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 Open
+// function should be called just once. It is rarely necessary to
+// close a DB.
func Open(driverName, dataSourceName string) (*DB, error) {
driveri, ok := drivers[driverName]
if !ok {
@@ -452,6 +458,9 @@ func (db *DB) Ping() error {
}
// Close closes the database, releasing any open resources.
+//
+// It is rare to Close a DB, as the DB handle is meant to be
+// long-lived and shared between many goroutines.
func (db *DB) Close() error {
db.mu.Lock()
if db.closed { // Make DB.Close idempotent
@@ -569,7 +578,7 @@ func (db *DB) maybeOpenNewConnections() {
}
}
-// Runs in a seperate goroutine, opens new connections when requested.
+// Runs in a separate goroutine, opens new connections when requested.
func (db *DB) connectionOpener() {
for _ = range db.openerCh {
db.openNewConnection()
@@ -652,13 +661,16 @@ func (db *DB) conn() (*driverConn, error) {
return conn, nil
}
+ db.numOpen++ // optimistically
db.mu.Unlock()
ci, err := db.driver.Open(db.dsn)
if err != nil {
+ db.mu.Lock()
+ db.numOpen-- // correct for earlier optimism
+ db.mu.Unlock()
return nil, err
}
db.mu.Lock()
- db.numOpen++
dc := &driverConn{
db: db,
ci: ci,
@@ -774,11 +786,11 @@ func (db *DB) putConn(dc *driverConn, err error) {
// Satisfy a connRequest or put the driverConn in the idle pool and return true
// or return false.
// putConnDBLocked will satisfy a connRequest if there is one, or it will
-// return the *driverConn to the freeConn list if err != nil and the idle
-// connection limit would not be reached.
+// return the *driverConn to the freeConn list if err == nil and the idle
+// connection limit will not be exceeded.
// If err != nil, the value of dc is ignored.
// If err == nil, then dc must not equal nil.
-// If a connRequest was fullfilled or the *driverConn was placed in the
+// If a connRequest was fulfilled or the *driverConn was placed in the
// freeConn list, then true is returned, otherwise false is returned.
func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
if db.connRequests.Len() > 0 {
@@ -791,20 +803,24 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
req <- dc
}
return true
- } else if err == nil && !db.closed && db.maxIdleConnsLocked() > 0 && db.maxIdleConnsLocked() > db.freeConn.Len() {
+ } else if err == nil && !db.closed && db.maxIdleConnsLocked() > db.freeConn.Len() {
dc.listElem = db.freeConn.PushFront(dc)
return true
}
return false
}
+// maxBadConnRetries is the number of maximum retries if the driver returns
+// driver.ErrBadConn to signal a broken connection.
+const maxBadConnRetries = 10
+
// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the
// returned statement.
func (db *DB) Prepare(query string) (*Stmt, error) {
var stmt *Stmt
var err error
- for i := 0; i < 10; i++ {
+ for i := 0; i < maxBadConnRetries; i++ {
stmt, err = db.prepare(query)
if err != driver.ErrBadConn {
break
@@ -846,7 +862,7 @@ func (db *DB) prepare(query string) (*Stmt, error) {
func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
var res Result
var err error
- for i := 0; i < 10; i++ {
+ for i := 0; i < maxBadConnRetries; i++ {
res, err = db.exec(query, args)
if err != driver.ErrBadConn {
break
@@ -895,7 +911,7 @@ func (db *DB) exec(query string, args []interface{}) (res Result, err error) {
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
var rows *Rows
var err error
- for i := 0; i < 10; i++ {
+ for i := 0; i < maxBadConnRetries; i++ {
rows, err = db.query(query, args)
if err != driver.ErrBadConn {
break
@@ -983,7 +999,7 @@ func (db *DB) QueryRow(query string, args ...interface{}) *Row {
func (db *DB) Begin() (*Tx, error) {
var tx *Tx
var err error
- for i := 0; i < 10; i++ {
+ for i := 0; i < maxBadConnRetries; i++ {
tx, err = db.begin()
if err != driver.ErrBadConn {
break
@@ -1245,13 +1261,24 @@ type Stmt struct {
func (s *Stmt) Exec(args ...interface{}) (Result, error) {
s.closemu.RLock()
defer s.closemu.RUnlock()
- dc, releaseConn, si, err := s.connStmt()
- if err != nil {
- return nil, err
- }
- defer releaseConn(nil)
- return resultFromStatement(driverStmt{dc, si}, args...)
+ var res Result
+ for i := 0; i < maxBadConnRetries; i++ {
+ dc, releaseConn, si, err := s.connStmt()
+ if err != nil {
+ if err == driver.ErrBadConn {
+ continue
+ }
+ return nil, err
+ }
+
+ res, err = resultFromStatement(driverStmt{dc, si}, args...)
+ releaseConn(err)
+ if err != driver.ErrBadConn {
+ return res, err
+ }
+ }
+ return nil, driver.ErrBadConn
}
func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) {
@@ -1329,26 +1356,21 @@ func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.St
// Make a new conn if all are busy.
// TODO(bradfitz): or wait for one? make configurable later?
if !match {
- for i := 0; ; i++ {
- dc, err := s.db.conn()
- if err != nil {
- return nil, nil, nil, err
- }
- dc.Lock()
- si, err := dc.prepareLocked(s.query)
- dc.Unlock()
- if err == driver.ErrBadConn && i < 10 {
- continue
- }
- if err != nil {
- return nil, nil, nil, err
- }
- s.mu.Lock()
- cs = connStmt{dc, si}
- s.css = append(s.css, cs)
- s.mu.Unlock()
- break
+ dc, err := s.db.conn()
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ dc.Lock()
+ si, err := dc.prepareLocked(s.query)
+ dc.Unlock()
+ if err != nil {
+ s.db.putConn(dc, err)
+ return nil, nil, nil, err
}
+ s.mu.Lock()
+ cs = connStmt{dc, si}
+ s.css = append(s.css, cs)
+ s.mu.Unlock()
}
conn := cs.dc
@@ -1361,31 +1383,39 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
s.closemu.RLock()
defer s.closemu.RUnlock()
- dc, releaseConn, si, err := s.connStmt()
- if err != nil {
- return nil, err
- }
+ var rowsi driver.Rows
+ for i := 0; i < maxBadConnRetries; i++ {
+ dc, releaseConn, si, err := s.connStmt()
+ if err != nil {
+ if err == driver.ErrBadConn {
+ continue
+ }
+ return nil, err
+ }
- ds := driverStmt{dc, si}
- rowsi, err := rowsiFromStatement(ds, args...)
- if err != nil {
- releaseConn(err)
- return nil, err
- }
+ rowsi, err = rowsiFromStatement(driverStmt{dc, si}, args...)
+ if err == nil {
+ // Note: ownership of ci passes to the *Rows, to be freed
+ // with releaseConn.
+ rows := &Rows{
+ dc: dc,
+ rowsi: rowsi,
+ // releaseConn set below
+ }
+ s.db.addDep(s, rows)
+ rows.releaseConn = func(err error) {
+ releaseConn(err)
+ s.db.removeDep(s, rows)
+ }
+ return rows, nil
+ }
- // Note: ownership of ci passes to the *Rows, to be freed
- // with releaseConn.
- rows := &Rows{
- dc: dc,
- rowsi: rowsi,
- // releaseConn set below
- }
- s.db.addDep(s, rows)
- rows.releaseConn = func(err error) {
releaseConn(err)
- s.db.removeDep(s, rows)
+ if err != driver.ErrBadConn {
+ return nil, err
+ }
}
- return rows, nil
+ return nil, driver.ErrBadConn
}
func rowsiFromStatement(ds driverStmt, args ...interface{}) (driver.Rows, error) {
@@ -1476,6 +1506,7 @@ func (s *Stmt) finalClose() error {
//
// rows, err := db.Query("SELECT ...")
// ...
+// defer rows.Close()
// for rows.Next() {
// var id int
// var name string
@@ -1495,10 +1526,12 @@ type Rows struct {
closeStmt driver.Stmt // if non-nil, statement to Close on close
}
-// Next prepares the next result row for reading with the Scan method.
-// It returns true on success, false if there is no next result row.
-// Every call to Scan, even the first one, must be preceded by a call
-// to Next.
+// Next prepares the next result row for reading with the Scan method. It
+// returns true on success, or false if there is no next result row or an error
+// happened while preparing it. Err should be consulted to distinguish between
+// the two cases.
+//
+// Every call to Scan, even the first one, must be preceded by a call to Next.
func (rs *Rows) Next() bool {
if rs.closed {
return false
@@ -1625,12 +1658,19 @@ func (r *Row) Scan(dest ...interface{}) error {
}
if !r.rows.Next() {
+ if err := r.rows.Err(); err != nil {
+ return err
+ }
return ErrNoRows
}
err := r.rows.Scan(dest...)
if err != nil {
return err
}
+ // Make sure the query can be processed to completion with no errors.
+ if err := r.rows.Close(); err != nil {
+ return err
+ }
return nil
}
diff --git a/src/pkg/database/sql/sql_test.go b/src/pkg/database/sql/sql_test.go
index 787a5c9f7..7971f1491 100644
--- a/src/pkg/database/sql/sql_test.go
+++ b/src/pkg/database/sql/sql_test.go
@@ -348,7 +348,6 @@ func TestStatementQueryRow(t *testing.T) {
t.Errorf("%d: age=%d, want %d", n, age, tt.want)
}
}
-
}
// golang.org/issue/3734
@@ -462,7 +461,7 @@ func TestTxStmt(t *testing.T) {
}
// Issue: http://golang.org/issue/2784
-// This test didn't fail before because we got luckly with the fakedb driver.
+// This test didn't fail before because we got lucky with the fakedb driver.
// It was failing, and now not, in github.com/bradfitz/go-sql-test
func TestTxQuery(t *testing.T) {
db := newTestDB(t, "")
@@ -660,6 +659,35 @@ func TestQueryRowClosingStmt(t *testing.T) {
}
}
+// Test issue 6651
+func TestIssue6651(t *testing.T) {
+ db := newTestDB(t, "people")
+ defer closeDB(t, db)
+
+ var v string
+
+ want := "error in rows.Next"
+ rowsCursorNextHook = func(dest []driver.Value) error {
+ return fmt.Errorf(want)
+ }
+ defer func() { rowsCursorNextHook = nil }()
+ err := db.QueryRow("SELECT|people|name|").Scan(&v)
+ if err == nil || err.Error() != want {
+ t.Errorf("error = %q; want %q", err, want)
+ }
+ rowsCursorNextHook = nil
+
+ want = "error in rows.Close"
+ rowsCloseHook = func(rows *Rows, err *error) {
+ *err = fmt.Errorf(want)
+ }
+ defer func() { rowsCloseHook = nil }()
+ err = db.QueryRow("SELECT|people|name|").Scan(&v)
+ if err == nil || err.Error() != want {
+ t.Errorf("error = %q; want %q", err, want)
+ }
+}
+
type nullTestRow struct {
nullParam interface{}
notNullParam interface{}
@@ -1249,6 +1277,111 @@ func TestStmtCloseOrder(t *testing.T) {
}
}
+// golang.org/issue/5781
+func TestErrBadConnReconnect(t *testing.T) {
+ db := newTestDB(t, "foo")
+ defer closeDB(t, db)
+ exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
+
+ simulateBadConn := func(name string, hook *func() bool, op func() error) {
+ broken, retried := false, false
+ numOpen := db.numOpen
+
+ // simulate a broken connection on the first try
+ *hook = func() bool {
+ if !broken {
+ broken = true
+ return true
+ }
+ retried = true
+ return false
+ }
+
+ if err := op(); err != nil {
+ t.Errorf(name+": %v", err)
+ return
+ }
+
+ if !broken || !retried {
+ t.Error(name + ": Failed to simulate broken connection")
+ }
+ *hook = nil
+
+ if numOpen != db.numOpen {
+ t.Errorf(name+": leaked %d connection(s)!", db.numOpen-numOpen)
+ numOpen = db.numOpen
+ }
+ }
+
+ // db.Exec
+ dbExec := func() error {
+ _, err := db.Exec("INSERT|t1|name=?,age=?,dead=?", "Gordon", 3, true)
+ return err
+ }
+ simulateBadConn("db.Exec prepare", &hookPrepareBadConn, dbExec)
+ simulateBadConn("db.Exec exec", &hookExecBadConn, dbExec)
+
+ // db.Query
+ dbQuery := func() error {
+ rows, err := db.Query("SELECT|t1|age,name|")
+ if err == nil {
+ err = rows.Close()
+ }
+ return err
+ }
+ simulateBadConn("db.Query prepare", &hookPrepareBadConn, dbQuery)
+ simulateBadConn("db.Query query", &hookQueryBadConn, dbQuery)
+
+ // db.Prepare
+ simulateBadConn("db.Prepare", &hookPrepareBadConn, func() error {
+ stmt, err := db.Prepare("INSERT|t1|name=?,age=?,dead=?")
+ if err != nil {
+ return err
+ }
+ stmt.Close()
+ return nil
+ })
+
+ // stmt.Exec
+ stmt1, err := db.Prepare("INSERT|t1|name=?,age=?,dead=?")
+ if err != nil {
+ t.Fatalf("prepare: %v", err)
+ }
+ defer stmt1.Close()
+ // make sure we must prepare the stmt first
+ for _, cs := range stmt1.css {
+ cs.dc.inUse = true
+ }
+
+ stmtExec := func() error {
+ _, err := stmt1.Exec("Gopher", 3, false)
+ return err
+ }
+ simulateBadConn("stmt.Exec prepare", &hookPrepareBadConn, stmtExec)
+ simulateBadConn("stmt.Exec exec", &hookExecBadConn, stmtExec)
+
+ // stmt.Query
+ stmt2, err := db.Prepare("SELECT|t1|age,name|")
+ if err != nil {
+ t.Fatalf("prepare: %v", err)
+ }
+ defer stmt2.Close()
+ // make sure we must prepare the stmt first
+ for _, cs := range stmt2.css {
+ cs.dc.inUse = true
+ }
+
+ stmtQuery := func() error {
+ rows, err := stmt2.Query()
+ if err == nil {
+ err = rows.Close()
+ }
+ return err
+ }
+ simulateBadConn("stmt.Query prepare", &hookPrepareBadConn, stmtQuery)
+ simulateBadConn("stmt.Query exec", &hookQueryBadConn, stmtQuery)
+}
+
type concurrentTest interface {
init(t testing.TB, db *DB)
finish(t testing.TB)