summaryrefslogtreecommitdiff
path: root/libgo/go/database
diff options
context:
space:
mode:
authorian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2016-02-03 21:58:02 +0000
committerian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2016-02-03 21:58:02 +0000
commit0694cef2844753fb80be4f71f7d2eb82eb5ba464 (patch)
tree2f8da9862a9c1fe0df138917f997b03439c02773 /libgo/go/database
parent397fecd695789eccab667bf771a354df71d843e8 (diff)
downloadgcc-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.go38
-rw-r--r--libgo/go/database/sql/convert_test.go61
-rw-r--r--libgo/go/database/sql/driver/types.go13
-rw-r--r--libgo/go/database/sql/fakedb_test.go59
-rw-r--r--libgo/go/database/sql/sql.go258
-rw-r--r--libgo/go/database/sql/sql_test.go341
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)