diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2016-02-03 21:58:02 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2016-02-03 21:58:02 +0000 |
commit | 0694cef2844753fb80be4f71f7d2eb82eb5ba464 (patch) | |
tree | 2f8da9862a9c1fe0df138917f997b03439c02773 /libgo/go/database | |
parent | 397fecd695789eccab667bf771a354df71d843e8 (diff) | |
download | gcc-0694cef2844753fb80be4f71f7d2eb82eb5ba464.tar.gz |
libgo: Update to go1.6rc1.
Reviewed-on: https://go-review.googlesource.com/19200
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@233110 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/database')
-rw-r--r-- | libgo/go/database/sql/convert.go | 38 | ||||
-rw-r--r-- | libgo/go/database/sql/convert_test.go | 61 | ||||
-rw-r--r-- | libgo/go/database/sql/driver/types.go | 13 | ||||
-rw-r--r-- | libgo/go/database/sql/fakedb_test.go | 59 | ||||
-rw-r--r-- | libgo/go/database/sql/sql.go | 258 | ||||
-rw-r--r-- | libgo/go/database/sql/sql_test.go | 341 |
6 files changed, 665 insertions, 105 deletions
diff --git a/libgo/go/database/sql/convert.go b/libgo/go/database/sql/convert.go index c0b38a24940..740fd9d6e7c 100644 --- a/libgo/go/database/sql/convert.go +++ b/libgo/go/database/sql/convert.go @@ -12,6 +12,7 @@ import ( "fmt" "reflect" "strconv" + "time" ) var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error @@ -127,6 +128,18 @@ func convertAssign(dest, src interface{}) error { *d = s return nil } + case time.Time: + switch d := dest.(type) { + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + } case nil: switch d := dest.(type) { case *interface{}: @@ -203,11 +216,16 @@ func convertAssign(dest, src interface{}) error { } dv := reflect.Indirect(dpv) - if dv.Kind() == sv.Kind() { + if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { dv.Set(sv) return nil } + if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) { + dv.Set(sv.Convert(dv.Type())) + return nil + } + switch dv.Kind() { case reflect.Ptr: if src == nil { @@ -221,7 +239,8 @@ func convertAssign(dest, src interface{}) error { s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetInt(i64) return nil @@ -229,7 +248,8 @@ func convertAssign(dest, src interface{}) error { s := asString(src) u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetUint(u64) return nil @@ -237,13 +257,21 @@ func convertAssign(dest, src interface{}) error { s := asString(src) f64, err := strconv.ParseFloat(s, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetFloat(f64) return nil } - return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) +} + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err } func cloneBytes(b []byte) []byte { diff --git a/libgo/go/database/sql/convert_test.go b/libgo/go/database/sql/convert_test.go index 98af9fb64c5..342875e190c 100644 --- a/libgo/go/database/sql/convert_test.go +++ b/libgo/go/database/sql/convert_test.go @@ -16,23 +16,28 @@ import ( var someTime = time.Unix(123, 0) var answer int64 = 42 +type userDefined float64 + +type userDefinedSlice []int + type conversionTest struct { s, d interface{} // source and destination // following are used if they're non-zero - wantint int64 - wantuint uint64 - wantstr string - wantbytes []byte - wantraw RawBytes - wantf32 float32 - wantf64 float64 - wanttime time.Time - wantbool bool // used if d is of type *bool - wanterr string - wantiface interface{} - wantptr *int64 // if non-nil, *d's pointed value must be equal to *wantptr - wantnil bool // if true, *d must be *int64(nil) + wantint int64 + wantuint uint64 + wantstr string + wantbytes []byte + wantraw RawBytes + wantf32 float32 + wantf64 float64 + wanttime time.Time + wantbool bool // used if d is of type *bool + wanterr string + wantiface interface{} + wantptr *int64 // if non-nil, *d's pointed value must be equal to *wantptr + wantnil bool // if true, *d must be *int64(nil) + wantusrdef userDefined } // Target variables for scanning into. @@ -72,6 +77,14 @@ var conversionTests = []conversionTest{ {s: uint64(123), d: &scanstr, wantstr: "123"}, {s: 1.5, d: &scanstr, wantstr: "1.5"}, + // From time.Time: + {s: time.Unix(1, 0).UTC(), d: &scanstr, wantstr: "1970-01-01T00:00:01Z"}, + {s: time.Unix(1453874597, 0).In(time.FixedZone("here", -3600*8)), d: &scanstr, wantstr: "2016-01-26T22:03:17-08:00"}, + {s: time.Unix(1, 2).UTC(), d: &scanstr, wantstr: "1970-01-01T00:00:01.000000002Z"}, + {s: time.Time{}, d: &scanstr, wantstr: "0001-01-01T00:00:00Z"}, + {s: time.Unix(1, 2).UTC(), d: &scanbytes, wantbytes: []byte("1970-01-01T00:00:01.000000002Z")}, + {s: time.Unix(1, 2).UTC(), d: &scaniface, wantiface: time.Unix(1, 2).UTC()}, + // To []byte {s: nil, d: &scanbytes, wantbytes: nil}, {s: "string", d: &scanbytes, wantbytes: []byte("string")}, @@ -99,10 +112,16 @@ var conversionTests = []conversionTest{ // Strings to integers {s: "255", d: &scanuint8, wantuint: 255}, - {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: strconv.ParseUint: parsing "256": value out of range`}, + {s: "256", d: &scanuint8, wanterr: "converting driver.Value type string (\"256\") to a uint8: value out of range"}, {s: "256", d: &scanuint16, wantuint: 256}, {s: "-1", d: &scanint, wantint: -1}, - {s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: strconv.ParseInt: parsing "foo": invalid syntax`}, + {s: "foo", d: &scanint, wanterr: "converting driver.Value type string (\"foo\") to a int: invalid syntax"}, + + // int64 to smaller integers + {s: int64(5), d: &scanuint8, wantuint: 5}, + {s: int64(256), d: &scanuint8, wanterr: "converting driver.Value type int64 (\"256\") to a uint8: value out of range"}, + {s: int64(256), d: &scanuint16, wantuint: 256}, + {s: int64(65536), d: &scanuint16, wanterr: "converting driver.Value type int64 (\"65536\") to a uint16: value out of range"}, // True bools {s: true, d: &scanbool, wantbool: true}, @@ -145,6 +164,15 @@ var conversionTests = []conversionTest{ {s: true, d: &scaniface, wantiface: true}, {s: nil, d: &scaniface}, {s: []byte(nil), d: &scaniface, wantiface: []byte(nil)}, + + // To a user-defined type + {s: 1.5, d: new(userDefined), wantusrdef: 1.5}, + {s: int64(123), d: new(userDefined), wantusrdef: 123}, + {s: "1.5", d: new(userDefined), wantusrdef: 1.5}, + {s: []byte{1, 2, 3}, d: new(userDefinedSlice), wanterr: `unsupported Scan, storing driver.Value type []uint8 into type *sql.userDefinedSlice`}, + + // Other errors + {s: complex(1, 2), d: &scanstr, wanterr: `unsupported Scan, storing driver.Value type complex128 into type *string`}, } func intPtrValue(intptr interface{}) interface{} { @@ -228,6 +256,9 @@ func TestConversions(t *testing.T) { } } } + if ct.wantusrdef != 0 && ct.wantusrdef != *ct.d.(*userDefined) { + errf("want userDefined %f, got %f", ct.wantusrdef, *ct.d.(*userDefined)) + } } } diff --git a/libgo/go/database/sql/driver/types.go b/libgo/go/database/sql/driver/types.go index 3305354dfd0..bc547849897 100644 --- a/libgo/go/database/sql/driver/types.go +++ b/libgo/go/database/sql/driver/types.go @@ -200,10 +200,15 @@ func IsScanValue(v interface{}) bool { // ValueConverter that's used when a Stmt doesn't implement // ColumnConverter. // -// DefaultParameterConverter returns the given value directly if -// IsValue(value). Otherwise integer type are converted to -// int64, floats to float64, and strings to []byte. Other types are -// an error. +// DefaultParameterConverter returns its argument directly if +// IsValue(arg). Otherwise, if the argument implements Valuer, its +// Value method is used to return a Value. As a fallback, the provided +// argument's underlying type is used to convert it to a Value: +// underlying integer types are converted to int64, floats to float64, +// and strings to []byte. If the argument is a nil pointer, +// ConvertValue returns a nil Value. If the argument is a non-nil +// pointer, it is dereferenced and ConvertValue is called +// recursively. Other types are an error. var DefaultParameterConverter defaultConverter type defaultConverter struct{} diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go index 8cbbb29a7c2..b5ff1213587 100644 --- a/libgo/go/database/sql/fakedb_test.go +++ b/libgo/go/database/sql/fakedb_test.go @@ -33,6 +33,9 @@ var _ = log.Printf // INSERT|<tablename>|col=val,col2=val2,col3=? // SELECT|<tablename>|projectcol1,projectcol2|filtercol=?,filtercol2=? // +// Any of these can be preceded by PANIC|<method>|, to cause the +// named method on fakeStmt to panic. +// // When opening a fakeDriver's database, it starts empty with no // tables. All tables and data are stored in memory only. type fakeDriver struct { @@ -111,6 +114,7 @@ type fakeStmt struct { cmd string table string + panic string closed bool @@ -153,12 +157,32 @@ func TestDrivers(t *testing.T) { } } +// hook to simulate connection failures +var hookOpenErr struct { + sync.Mutex + fn func() error +} + +func setHookOpenErr(fn func() error) { + hookOpenErr.Lock() + defer hookOpenErr.Unlock() + hookOpenErr.fn = fn +} + // Supports dsn forms: // <dbname> // <dbname>;<opts> (only currently supported option is `badConn`, // which causes driver.ErrBadConn to be returned on // every other conn.Begin()) func (d *fakeDriver) Open(dsn string) (driver.Conn, error) { + hookOpenErr.Lock() + fn := hookOpenErr.fn + hookOpenErr.Unlock() + if fn != nil { + if err := fn(); err != nil { + return nil, err + } + } parts := strings.Split(dsn, ";") if len(parts) < 1 { return nil, errors.New("fakedb: no database name") @@ -479,9 +503,15 @@ func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { if len(parts) < 1 { return nil, errf("empty query") } + stmt := &fakeStmt{q: query, c: c} + if len(parts) >= 3 && parts[0] == "PANIC" { + stmt.panic = parts[1] + parts = parts[2:] + } cmd := parts[0] + stmt.cmd = cmd parts = parts[1:] - stmt := &fakeStmt{q: query, c: c, cmd: cmd} + c.incrStat(&c.stmtsMade) switch cmd { case "WIPE": @@ -504,6 +534,9 @@ func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { } func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter { + if s.panic == "ColumnConverter" { + panic(s.panic) + } if len(s.placeholderConverter) == 0 { return driver.DefaultParameterConverter } @@ -511,6 +544,9 @@ func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter { } func (s *fakeStmt) Close() error { + if s.panic == "Close" { + panic(s.panic) + } if s.c == nil { panic("nil conn in fakeStmt.Close") } @@ -530,6 +566,9 @@ var errClosed = errors.New("fakedb: statement has been closed") var hookExecBadConn func() bool func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) { + if s.panic == "Exec" { + panic(s.panic) + } if s.closed { return nil, errClosed } @@ -614,6 +653,9 @@ func (s *fakeStmt) execInsert(args []driver.Value, doInsert bool) (driver.Result var hookQueryBadConn func() bool func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) { + if s.panic == "Query" { + panic(s.panic) + } if s.closed { return nil, errClosed } @@ -696,16 +738,31 @@ rows: } func (s *fakeStmt) NumInput() int { + if s.panic == "NumInput" { + panic(s.panic) + } return s.placeholders } +// hook to simulate broken connections +var hookCommitBadConn func() bool + func (tx *fakeTx) Commit() error { tx.c.currTx = nil + if hookCommitBadConn != nil && hookCommitBadConn() { + return driver.ErrBadConn + } return nil } +// hook to simulate broken connections +var hookRollbackBadConn func() bool + func (tx *fakeTx) Rollback() error { tx.c.currTx = nil + if hookRollbackBadConn != nil && hookRollbackBadConn() { + return driver.ErrBadConn + } return nil } diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go index aaa4ea28be4..d8e7cb77af3 100644 --- a/libgo/go/database/sql/sql.go +++ b/libgo/go/database/sql/sql.go @@ -21,13 +21,17 @@ import ( "sort" "sync" "sync/atomic" + "time" ) var ( - driversMu sync.Mutex + driversMu sync.RWMutex drivers = make(map[string]driver.Driver) ) +// nowFunc returns the current time; it's overridden in tests. +var nowFunc = time.Now + // Register makes a database driver available by the provided name. // If Register is called twice with the same name or if driver is nil, // it panics. @@ -52,8 +56,8 @@ func unregisterAllDrivers() { // Drivers returns a sorted list of the names of the registered drivers. func Drivers() []string { - driversMu.Lock() - defer driversMu.Unlock() + driversMu.RLock() + defer driversMu.RUnlock() var list []string for name := range drivers { list = append(list, name) @@ -185,8 +189,7 @@ func (n NullBool) Value() (driver.Value, error) { type Scanner interface { // Scan assigns a value from a database driver. // - // The src value will be of one of the following restricted - // set of types: + // The src value will be of one of the following types: // // int64 // float64 @@ -229,19 +232,20 @@ type DB struct { mu sync.Mutex // protects following fields freeConn []*driverConn connRequests []chan connRequest - numOpen int - pendingOpens int + numOpen int // number of opened and pending open connections // Used to signal the need for new connections // a goroutine running connectionOpener() reads on this chan and // maybeOpenNewConnections sends on the chan (one send per needed connection) // It is closed during db.Close(). The close tells the connectionOpener // goroutine to exit. - openerCh chan struct{} - closed bool - dep map[finalCloser]depSet - lastPut map[*driverConn]string // stacktrace of last conn's put; debug only - maxIdle int // zero means defaultMaxIdleConns; negative means 0 - maxOpen int // <= 0 means unlimited + openerCh chan struct{} + closed bool + dep map[finalCloser]depSet + lastPut map[*driverConn]string // stacktrace of last conn's put; debug only + maxIdle int // zero means defaultMaxIdleConns; negative means 0 + maxOpen int // <= 0 means unlimited + maxLifetime time.Duration // maximum amount of time a connection may be reused + cleanerCh chan struct{} } // connReuseStrategy determines how (*DB).conn returns database connections. @@ -261,7 +265,8 @@ const ( // interfaces returned via that Conn, such as calls on Tx, Stmt, // Result, Rows) type driverConn struct { - db *DB + db *DB + createdAt time.Time sync.Mutex // guards following ci driver.Conn @@ -285,6 +290,13 @@ func (dc *driverConn) removeOpenStmt(si driver.Stmt) { delete(dc.openStmt, si) } +func (dc *driverConn) expired(timeout time.Duration) bool { + if timeout <= 0 { + return false + } + return dc.createdAt.Add(timeout).Before(nowFunc()) +} + func (dc *driverConn) prepareLocked(query string) (driver.Stmt, error) { si, err := dc.ci.Prepare(query) if err == nil { @@ -441,7 +453,7 @@ func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error { } } -// This is the size of the connectionOpener request chan (dn.openerCh). +// This is the size of the connectionOpener request chan (DB.openerCh). // 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 @@ -466,9 +478,9 @@ var connectionRequestQueueSize = 1000000 // function should be called just once. It is rarely necessary to // close a DB. func Open(driverName, dataSourceName string) (*DB, error) { - driversMu.Lock() + driversMu.RLock() driveri, ok := drivers[driverName] - driversMu.Unlock() + driversMu.RUnlock() if !ok { return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) } @@ -507,6 +519,9 @@ func (db *DB) Close() error { return nil } close(db.openerCh) + if db.cleanerCh != nil { + close(db.cleanerCh) + } var err error fns := make([]func() error, 0, len(db.freeConn)) for _, dc := range db.freeConn { @@ -595,6 +610,84 @@ func (db *DB) SetMaxOpenConns(n int) { } } +// SetConnMaxLifetime sets the maximum amount of time a connection may be reused. +// +// Expired connections may be closed lazily before reuse. +// +// If d <= 0, connections are reused forever. +func (db *DB) SetConnMaxLifetime(d time.Duration) { + if d < 0 { + d = 0 + } + db.mu.Lock() + // wake cleaner up when lifetime is shortened. + if d > 0 && d < db.maxLifetime && db.cleanerCh != nil { + select { + case db.cleanerCh <- struct{}{}: + default: + } + } + db.maxLifetime = d + db.startCleanerLocked() + db.mu.Unlock() +} + +// startCleanerLocked starts connectionCleaner if needed. +func (db *DB) startCleanerLocked() { + if db.maxLifetime > 0 && db.numOpen > 0 && db.cleanerCh == nil { + db.cleanerCh = make(chan struct{}, 1) + go db.connectionCleaner(db.maxLifetime) + } +} + +func (db *DB) connectionCleaner(d time.Duration) { + const minInterval = time.Second + + if d < minInterval { + d = minInterval + } + t := time.NewTimer(d) + + for { + select { + case <-t.C: + case <-db.cleanerCh: // maxLifetime was changed or db was closed. + } + + db.mu.Lock() + d = db.maxLifetime + if db.closed || db.numOpen == 0 || d <= 0 { + db.cleanerCh = nil + db.mu.Unlock() + return + } + + expiredSince := nowFunc().Add(-d) + var closing []*driverConn + for i := 0; i < len(db.freeConn); i++ { + c := db.freeConn[i] + if c.createdAt.Before(expiredSince) { + closing = append(closing, c) + last := len(db.freeConn) - 1 + db.freeConn[i] = db.freeConn[last] + db.freeConn[last] = nil + db.freeConn = db.freeConn[:last] + i-- + } + } + db.mu.Unlock() + + for _, c := range closing { + c.Close() + } + + if d < minInterval { + d = minInterval + } + t.Reset(d) + } +} + // DBStats contains database statistics. type DBStats struct { // OpenConnections is the number of open connections to the database. @@ -615,15 +708,15 @@ func (db *DB) Stats() DBStats { // If there are connRequests and the connection limit hasn't been reached, // then tell the connectionOpener to open new connections. func (db *DB) maybeOpenNewConnections() { - numRequests := len(db.connRequests) - db.pendingOpens + numRequests := len(db.connRequests) if db.maxOpen > 0 { - numCanOpen := db.maxOpen - (db.numOpen + db.pendingOpens) + numCanOpen := db.maxOpen - db.numOpen if numRequests > numCanOpen { numRequests = numCanOpen } } for numRequests > 0 { - db.pendingOpens++ + db.numOpen++ // optimistically numRequests-- db.openerCh <- struct{}{} } @@ -638,6 +731,9 @@ func (db *DB) connectionOpener() { // Open one new connection func (db *DB) openNewConnection() { + // maybeOpenNewConnctions has already executed db.numOpen++ before it sent + // on db.openerCh. This function must execute db.numOpen-- if the + // connection fails or is closed before returning. ci, err := db.driver.Open(db.dsn) db.mu.Lock() defer db.mu.Unlock() @@ -645,21 +741,24 @@ func (db *DB) openNewConnection() { if err == nil { ci.Close() } + db.numOpen-- return } - db.pendingOpens-- if err != nil { + db.numOpen-- db.putConnDBLocked(nil, err) + db.maybeOpenNewConnections() return } dc := &driverConn{ - db: db, - ci: ci, + db: db, + createdAt: nowFunc(), + ci: ci, } if db.putConnDBLocked(dc, err) { db.addDepLocked(dc, dc) - db.numOpen++ } else { + db.numOpen-- ci.Close() } } @@ -681,6 +780,7 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { db.mu.Unlock() return nil, errDBClosed } + lifetime := db.maxLifetime // Prefer a free connection, if possible. numFree := len(db.freeConn) @@ -690,6 +790,10 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { db.freeConn = db.freeConn[:numFree-1] conn.inUse = true db.mu.Unlock() + if conn.expired(lifetime) { + conn.Close() + return nil, driver.ErrBadConn + } return conn, nil } @@ -701,7 +805,14 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { req := make(chan connRequest, 1) db.connRequests = append(db.connRequests, req) db.mu.Unlock() - ret := <-req + ret, ok := <-req + if !ok { + return nil, errDBClosed + } + if ret.err == nil && ret.conn.expired(lifetime) { + ret.conn.Close() + return nil, driver.ErrBadConn + } return ret.conn, ret.err } @@ -711,13 +822,15 @@ func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { if err != nil { db.mu.Lock() db.numOpen-- // correct for earlier optimism + db.maybeOpenNewConnections() db.mu.Unlock() return nil, err } db.mu.Lock() dc := &driverConn{ - db: db, - ci: ci, + db: db, + createdAt: nowFunc(), + ci: ci, } db.addDepLocked(dc, dc) dc.inUse = true @@ -827,6 +940,7 @@ func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { return true } else if err == nil && !db.closed && db.maxIdleConnsLocked() > len(db.freeConn) { db.freeConn = append(db.freeConn, dc) + db.startCleanerLocked() return true } return false @@ -1022,7 +1136,7 @@ func (db *DB) queryConn(dc *driverConn, releaseConn func(error), query string, a } // QueryRow executes a query that is expected to return at most one row. -// QueryRow always return a non-nil value. Errors are deferred until +// QueryRow always returns a non-nil value. Errors are deferred until // Row's Scan method is called. func (db *DB) QueryRow(query string, args ...interface{}) *Row { rows, err := db.Query(query, args...) @@ -1103,12 +1217,12 @@ type Tx struct { var ErrTxDone = errors.New("sql: Transaction has already been committed or rolled back") -func (tx *Tx) close() { +func (tx *Tx) close(err error) { if tx.done { panic("double close") // internal error } tx.done = true - tx.db.putConn(tx.dc, nil) + tx.db.putConn(tx.dc, err) tx.dc = nil tx.txi = nil } @@ -1134,13 +1248,13 @@ func (tx *Tx) Commit() error { if tx.done { return ErrTxDone } - defer tx.close() tx.dc.Lock() err := tx.txi.Commit() tx.dc.Unlock() if err != driver.ErrBadConn { tx.closePrepared() } + tx.close(err) return err } @@ -1149,13 +1263,13 @@ func (tx *Tx) Rollback() error { if tx.done { return ErrTxDone } - defer tx.close() tx.dc.Lock() err := tx.txi.Rollback() tx.dc.Unlock() if err != driver.ErrBadConn { tx.closePrepared() } + tx.close(err) return err } @@ -1296,7 +1410,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { } // QueryRow executes a query that is expected to return at most one row. -// QueryRow always return a non-nil value. Errors are deferred until +// QueryRow always returns a non-nil value. Errors are deferred until // Row's Scan method is called. func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { rows, err := tx.Query(query, args...) @@ -1362,10 +1476,14 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) { return nil, driver.ErrBadConn } -func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) { +func driverNumInput(ds driverStmt) int { ds.Lock() - want := ds.si.NumInput() - ds.Unlock() + defer ds.Unlock() // in case NumInput panics + return ds.si.NumInput() +} + +func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) { + want := driverNumInput(ds) // -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 @@ -1380,8 +1498,8 @@ func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) { } ds.Lock() + defer ds.Unlock() resi, err := ds.si.Exec(dargs) - ds.Unlock() if err != nil { return nil, err } @@ -1576,9 +1694,9 @@ func (s *Stmt) Close() error { s.closed = true if s.tx != nil { - s.txsi.Close() + err := s.txsi.Close() s.mu.Unlock() - return nil + return err } s.mu.Unlock() @@ -1667,17 +1785,56 @@ func (rs *Rows) Columns() ([]string, error) { } // Scan copies the columns in the current row into the values pointed -// at by dest. +// at by dest. The number of values in dest must be the same as the +// number of columns in Rows. +// +// Scan converts columns read from the database into the following +// common Go types and special types provided by the sql package: +// +// *string +// *[]byte +// *int, *int8, *int16, *int32, *int64 +// *uint, *uint8, *uint16, *uint32, *uint64 +// *bool +// *float32, *float64 +// *interface{} +// *RawBytes +// any type implementing Scanner (see Scanner docs) +// +// In the most simple case, if the type of the value from the source +// column is an integer, bool or string type T and dest is of type *T, +// Scan simply assigns the value through the pointer. // -// If an argument has type *[]byte, Scan saves in that argument a copy -// of the corresponding data. The copy is owned by the caller and can -// be modified and held indefinitely. The copy can be avoided by using -// an argument of type *RawBytes instead; see the documentation for -// RawBytes for restrictions on its use. +// Scan also converts between string and numeric types, as long as no +// information would be lost. While Scan stringifies all numbers +// scanned from numeric database columns into *string, scans into +// numeric types are checked for overflow. For example, a float64 with +// value 300 or a string with value "300" can scan into a uint16, but +// not into a uint8, though float64(255) or "255" can scan into a +// uint8. One exception is that scans of some float64 numbers to +// strings may lose information when stringifying. In general, scan +// floating point columns into *float64. +// +// If a dest argument has type *[]byte, Scan saves in that argument a +// copy of the corresponding data. The copy is owned by the caller and +// can be modified and held indefinitely. The copy can be avoided by +// using an argument of type *RawBytes instead; see the documentation +// for RawBytes for restrictions on its use. // // If an argument has type *interface{}, Scan copies the value -// provided by the underlying driver without conversion. If the value -// is of type []byte, a copy is made and the caller owns the result. +// provided by the underlying driver without conversion. When scanning +// from a source value of type []byte to *interface{}, a copy of the +// slice is made and the caller owns the result. +// +// Source values of type time.Time may be scanned into values of type +// *time.Time, *interface{}, *string, or *[]byte. When converting to +// the latter two, time.Format3339Nano is used. +// +// Source values of type bool may be scanned into types *bool, +// *interface{}, *string, *[]byte, or *RawBytes. +// +// For scanning into *bool, the source may be true, false, 1, 0, or +// string inputs parseable by strconv.ParseBool. func (rs *Rows) Scan(dest ...interface{}) error { if rs.closed { return errors.New("sql: Rows are closed") @@ -1726,8 +1883,9 @@ type Row struct { } // Scan copies the columns from the matched row into the values -// pointed at by dest. If more than one row matches the query, -// Scan uses the first row and discards the rest. If no row matches +// pointed at by dest. See the documentation on Rows.Scan for details. +// If more than one row matches the query, +// Scan uses the first row and discards the rest. If no row matches // the query, Scan returns ErrNoRows. func (r *Row) Scan(dest ...interface{}) error { if r.err != nil { @@ -1812,6 +1970,6 @@ func stack() string { // withLock runs while holding lk. func withLock(lk sync.Locker, fn func()) { lk.Lock() + defer lk.Unlock() // in case fn panics fn() - lk.Unlock() } diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go index 432a641b855..8ec70d99b02 100644 --- a/libgo/go/database/sql/sql_test.go +++ b/libgo/go/database/sql/sql_test.go @@ -68,6 +68,46 @@ func newTestDB(t testing.TB, name string) *DB { return db } +func TestDriverPanic(t *testing.T) { + // Test that if driver panics, database/sql does not deadlock. + db, err := Open("test", fakeDBName) + if err != nil { + t.Fatalf("Open: %v", err) + } + expectPanic := func(name string, f func()) { + defer func() { + err := recover() + if err == nil { + t.Fatalf("%s did not panic", name) + } + }() + f() + } + + expectPanic("Exec Exec", func() { db.Exec("PANIC|Exec|WIPE") }) + exec(t, db, "WIPE") // check not deadlocked + expectPanic("Exec NumInput", func() { db.Exec("PANIC|NumInput|WIPE") }) + exec(t, db, "WIPE") // check not deadlocked + expectPanic("Exec Close", func() { db.Exec("PANIC|Close|WIPE") }) + exec(t, db, "WIPE") // check not deadlocked + exec(t, db, "PANIC|Query|WIPE") // should run successfully: Exec does not call Query + exec(t, db, "WIPE") // check not deadlocked + + exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime") + + expectPanic("Query Query", func() { db.Query("PANIC|Query|SELECT|people|age,name|") }) + expectPanic("Query NumInput", func() { db.Query("PANIC|NumInput|SELECT|people|age,name|") }) + expectPanic("Query Close", func() { + rows, err := db.Query("PANIC|Close|SELECT|people|age,name|") + if err != nil { + t.Fatal(err) + } + rows.Close() + }) + db.Query("PANIC|Exec|SELECT|people|age,name|") // should run successfully: Query does not call Exec + exec(t, db, "WIPE") // check not deadlocked +} + func exec(t testing.TB, db *DB, query string, args ...interface{}) { _, err := db.Exec(query, args...) if err != nil { @@ -142,6 +182,20 @@ func (db *DB) numFreeConns() int { return len(db.freeConn) } +// clearAllConns closes all connections in db. +func (db *DB) clearAllConns(t *testing.T) { + db.SetMaxIdleConns(0) + + if g, w := db.numFreeConns(), 0; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + if n := db.numDepsPollUntil(0, time.Second); n > 0 { + t.Errorf("number of dependencies = %d; expected 0", n) + db.dumpDeps(t) + } +} + func (db *DB) dumpDeps(t *testing.T) { for fc := range db.dep { db.dumpDep(t, 0, fc, map[finalCloser]bool{}) @@ -356,6 +410,44 @@ func TestStatementQueryRow(t *testing.T) { } } +type stubDriverStmt struct { + err error +} + +func (s stubDriverStmt) Close() error { + return s.err +} + +func (s stubDriverStmt) NumInput() int { + return -1 +} + +func (s stubDriverStmt) Exec(args []driver.Value) (driver.Result, error) { + return nil, nil +} + +func (s stubDriverStmt) Query(args []driver.Value) (driver.Rows, error) { + return nil, nil +} + +// golang.org/issue/12798 +func TestStatementClose(t *testing.T) { + want := errors.New("STMT ERROR") + + tests := []struct { + stmt *Stmt + msg string + }{ + {&Stmt{stickyErr: want}, "stickyErr not propagated"}, + {&Stmt{tx: &Tx{}, txsi: &driverStmt{&sync.Mutex{}, stubDriverStmt{want}}}, "driverStmt.Close() error not propagated"}, + } + for _, test := range tests { + if err := test.stmt.Close(); err != want { + t.Errorf("%s. Got stmt.Close() = %v, want = %v", test.msg, err, want) + } + } +} + // golang.org/issue/3734 func TestStatementQueryRowConcurrent(t *testing.T) { db := newTestDB(t, "people") @@ -953,16 +1045,7 @@ func TestMaxOpenConns(t *testing.T) { // Force the number of open connections to 0 so we can get an accurate // count for the test - db.SetMaxIdleConns(0) - - if g, w := db.numFreeConns(), 0; g != w { - t.Errorf("free conns = %d; want %d", g, w) - } - - if n := db.numDepsPollUntil(0, time.Second); n > 0 { - t.Errorf("number of dependencies = %d; expected 0", n) - db.dumpDeps(t) - } + db.clearAllConns(t) driver.mu.Lock() opens0 := driver.openCount @@ -1058,16 +1141,7 @@ func TestMaxOpenConns(t *testing.T) { db.dumpDeps(t) } - db.SetMaxIdleConns(0) - - if g, w := db.numFreeConns(), 0; g != w { - t.Errorf("free conns = %d; want %d", g, w) - } - - if n := db.numDepsPollUntil(0, time.Second); n > 0 { - t.Errorf("number of dependencies = %d; expected 0", n) - db.dumpDeps(t) - } + db.clearAllConns(t) } // Issue 9453: tests that SetMaxOpenConns can be lowered at runtime @@ -1121,6 +1195,67 @@ func TestMaxOpenConnsOnBusy(t *testing.T) { } } +// Issue 10886: tests that all connection attempts return when more than +// DB.maxOpen connections are in flight and the first DB.maxOpen fail. +func TestPendingConnsAfterErr(t *testing.T) { + const ( + maxOpen = 2 + tryOpen = maxOpen*2 + 2 + ) + + db := newTestDB(t, "people") + defer closeDB(t, db) + defer func() { + for k, v := range db.lastPut { + t.Logf("%p: %v", k, v) + } + }() + + db.SetMaxOpenConns(maxOpen) + db.SetMaxIdleConns(0) + + errOffline := errors.New("db offline") + defer func() { setHookOpenErr(nil) }() + + errs := make(chan error, tryOpen) + + unblock := make(chan struct{}) + setHookOpenErr(func() error { + <-unblock // block until all connections are in flight + return errOffline + }) + + var opening sync.WaitGroup + opening.Add(tryOpen) + for i := 0; i < tryOpen; i++ { + go func() { + opening.Done() // signal one connection is in flight + _, err := db.Exec("INSERT|people|name=Julia,age=19") + errs <- err + }() + } + + opening.Wait() // wait for all workers to begin running + time.Sleep(10 * time.Millisecond) // make extra sure all workers are blocked + close(unblock) // let all workers proceed + + const timeout = 100 * time.Millisecond + to := time.NewTimer(timeout) + defer to.Stop() + + // check that all connections fail without deadlock + for i := 0; i < tryOpen; i++ { + select { + case err := <-errs: + if got, want := err, errOffline; got != want { + t.Errorf("unexpected err: got %v, want %v", got, want) + } + case <-to.C: + t.Fatalf("orphaned connection request(s), still waiting after %v", timeout) + } + } +} + func TestSingleOpenConn(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -1164,6 +1299,90 @@ func TestStats(t *testing.T) { } } +func TestConnMaxLifetime(t *testing.T) { + t0 := time.Unix(1000000, 0) + offset := time.Duration(0) + + nowFunc = func() time.Time { return t0.Add(offset) } + defer func() { nowFunc = time.Now }() + + db := newTestDB(t, "magicquery") + defer closeDB(t, db) + + driver := db.driver.(*fakeDriver) + + // Force the number of open connections to 0 so we can get an accurate + // count for the test + db.clearAllConns(t) + + driver.mu.Lock() + opens0 := driver.openCount + closes0 := driver.closeCount + driver.mu.Unlock() + + db.SetMaxIdleConns(10) + db.SetMaxOpenConns(10) + + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + offset = time.Second + tx2, err := db.Begin() + if err != nil { + t.Fatal(err) + } + + tx.Commit() + tx2.Commit() + + driver.mu.Lock() + opens := driver.openCount - opens0 + closes := driver.closeCount - closes0 + driver.mu.Unlock() + + if opens != 2 { + t.Errorf("opens = %d; want 2", opens) + } + if closes != 0 { + t.Errorf("closes = %d; want 0", closes) + } + if g, w := db.numFreeConns(), 2; g != w { + t.Errorf("free conns = %d; want %d", g, w) + } + + // Expire first conn + offset = time.Second * 11 + db.SetConnMaxLifetime(time.Second * 10) + if err != nil { + t.Fatal(err) + } + + tx, err = db.Begin() + if err != nil { + t.Fatal(err) + } + tx2, err = db.Begin() + if err != nil { + t.Fatal(err) + } + tx.Commit() + tx2.Commit() + + driver.mu.Lock() + opens = driver.openCount - opens0 + closes = driver.closeCount - closes0 + driver.mu.Unlock() + + if opens != 3 { + t.Errorf("opens = %d; want 3", opens) + } + if closes != 1 { + t.Errorf("closes = %d; want 1", closes) + } +} + // golang.org/issue/5323 func TestStmtCloseDeps(t *testing.T) { if testing.Short() { @@ -1257,16 +1476,7 @@ func TestStmtCloseDeps(t *testing.T) { db.dumpDeps(t) } - db.SetMaxIdleConns(0) - - if g, w := db.numFreeConns(), 0; g != w { - t.Errorf("free conns = %d; want %d", g, w) - } - - if n := db.numDepsPollUntil(0, time.Second); n > 0 { - t.Errorf("number of dependencies = %d; expected 0", n) - db.dumpDeps(t) - } + db.clearAllConns(t) } // golang.org/issue/5046 @@ -1564,6 +1774,77 @@ func TestErrBadConnReconnect(t *testing.T) { simulateBadConn("stmt.Query exec", &hookQueryBadConn, stmtQuery) } +// golang.org/issue/11264 +func TestTxEndBadConn(t *testing.T) { + db := newTestDB(t, "foo") + defer closeDB(t, db) + db.SetMaxIdleConns(0) + exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool") + db.SetMaxIdleConns(1) + + simulateBadConn := func(name string, hook *func() bool, op func() error) { + broken := false + numOpen := db.numOpen + + *hook = func() bool { + if !broken { + broken = true + } + return broken + } + + if err := op(); err != driver.ErrBadConn { + t.Errorf(name+": %v", err) + return + } + + if !broken { + t.Error(name + ": Failed to simulate broken connection") + } + *hook = nil + + if numOpen != db.numOpen { + t.Errorf(name+": leaked %d connection(s)!", db.numOpen-numOpen) + } + } + + // db.Exec + dbExec := func(endTx func(tx *Tx) error) func() error { + return func() error { + tx, err := db.Begin() + if err != nil { + return err + } + _, err = tx.Exec("INSERT|t1|name=?,age=?,dead=?", "Gordon", 3, true) + if err != nil { + return err + } + return endTx(tx) + } + } + simulateBadConn("db.Tx.Exec commit", &hookCommitBadConn, dbExec((*Tx).Commit)) + simulateBadConn("db.Tx.Exec rollback", &hookRollbackBadConn, dbExec((*Tx).Rollback)) + + // db.Query + dbQuery := func(endTx func(tx *Tx) error) func() error { + return func() error { + tx, err := db.Begin() + if err != nil { + return err + } + rows, err := tx.Query("SELECT|t1|age,name|") + if err == nil { + err = rows.Close() + } else { + return err + } + return endTx(tx) + } + } + simulateBadConn("db.Tx.Query commit", &hookCommitBadConn, dbQuery((*Tx).Commit)) + simulateBadConn("db.Tx.Query rollback", &hookRollbackBadConn, dbQuery((*Tx).Rollback)) +} + type concurrentTest interface { init(t testing.TB, db *DB) finish(t testing.TB) |