summaryrefslogtreecommitdiff
path: root/libgo/go/database
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2020-07-27 22:27:54 -0700
committerIan Lance Taylor <iant@golang.org>2020-08-01 11:21:40 -0700
commitf75af8c1464e948b5e166cf5ab09ebf0d82fc253 (patch)
tree3ba3299859b504bdeb477727471216bd094a0191 /libgo/go/database
parent75a23e59031fe673fc3b2e60fd1fe5f4c70ecb85 (diff)
downloadgcc-f75af8c1464e948b5e166cf5ab09ebf0d82fc253.tar.gz
libgo: update to go1.15rc1
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/245157
Diffstat (limited to 'libgo/go/database')
-rw-r--r--libgo/go/database/sql/driver/driver.go52
-rw-r--r--libgo/go/database/sql/fakedb_test.go2
-rw-r--r--libgo/go/database/sql/sql.go164
-rw-r--r--libgo/go/database/sql/sql_test.go79
4 files changed, 251 insertions, 46 deletions
diff --git a/libgo/go/database/sql/driver/driver.go b/libgo/go/database/sql/driver/driver.go
index a0ba7ecf694..5bbcf20db2f 100644
--- a/libgo/go/database/sql/driver/driver.go
+++ b/libgo/go/database/sql/driver/driver.go
@@ -6,6 +6,35 @@
// drivers as used by package sql.
//
// Most code should use package sql.
+//
+// The driver interface has evolved over time. Drivers should implement
+// Connector and DriverContext interfaces.
+// The Connector.Connect and Driver.Open methods should never return ErrBadConn.
+// ErrBadConn should only be returned from Validator, SessionResetter, or
+// a query method if the connection is already in an invalid (e.g. closed) state.
+//
+// All Conn implementations should implement the following interfaces:
+// Pinger, SessionResetter, and Validator.
+//
+// If named parameters or context are supported, the driver's Conn should implement:
+// ExecerContext, QueryerContext, ConnPrepareContext, and ConnBeginTx.
+//
+// To support custom data types, implement NamedValueChecker. NamedValueChecker
+// also allows queries to accept per-query options as a parameter by returning
+// ErrRemoveArgument from CheckNamedValue.
+//
+// If multiple result sets are supported, Rows should implement RowsNextResultSet.
+// If the driver knows how to describe the types present in the returned result
+// it should implement the following interfaces: RowsColumnTypeScanType,
+// RowsColumnTypeDatabaseTypeName, RowsColumnTypeLength, RowsColumnTypeNullable,
+// and RowsColumnTypePrecisionScale. A given row value may also return a Rows
+// type, which may represent a database cursor value.
+//
+// Before a connection is returned to the connection pool after use, IsValid is
+// called if implemented. Before a connection is reused for another query,
+// ResetSession is called if implemented. If a connection is never returned to the
+// connection pool but immediately reused, then ResetSession is called prior to
+// reuse but IsValid is not called.
package driver
import (
@@ -67,7 +96,7 @@ type Driver interface {
// If a Driver implements DriverContext, then sql.DB will call
// OpenConnector to obtain a Connector and then invoke
-// that Connector's Conn method to obtain each needed connection,
+// that Connector's Connect method to obtain each needed connection,
// instead of invoking the Driver's Open method for each connection.
// The two-step sequence allows drivers to parse the name just once
// and also provides access to per-Conn contexts.
@@ -94,7 +123,9 @@ type Connector interface {
//
// The provided context.Context is for dialing purposes only
// (see net.DialContext) and should not be stored or used for
- // other purposes.
+ // other purposes. A default timeout should still be used
+ // when dialing as a connection pool may call Connect
+ // asynchronously to any query.
//
// The returned connection is only used by one goroutine at a
// time.
@@ -205,6 +236,9 @@ type Conn interface {
// connections and only calls Close when there's a surplus of
// idle connections, it shouldn't be necessary for drivers to
// do their own connection caching.
+ //
+ // Drivers must ensure all network calls made by Close
+ // do not block indefinitely (e.g. apply a timeout).
Close() error
// Begin starts and returns a new transaction.
@@ -261,6 +295,17 @@ type SessionResetter interface {
ResetSession(ctx context.Context) error
}
+// Validator may be implemented by Conn to allow drivers to
+// signal if a connection is valid or if it should be discarded.
+//
+// If implemented, drivers may return the underlying error from queries,
+// even if the connection should be discarded by the connection pool.
+type Validator interface {
+ // IsValid is called prior to placing the connection into the
+ // connection pool. The connection will be discarded if false is returned.
+ IsValid() bool
+}
+
// Result is the result of a query execution.
type Result interface {
// LastInsertId returns the database's auto-generated ID
@@ -280,6 +325,9 @@ type Stmt interface {
//
// As of Go 1.1, a Stmt will not be closed if it's in use
// by any queries.
+ //
+ // Drivers must ensure all network calls made by Close
+ // do not block indefinitely (e.g. apply a timeout).
Close() error
// NumInput returns the number of placeholder parameters.
diff --git a/libgo/go/database/sql/fakedb_test.go b/libgo/go/database/sql/fakedb_test.go
index 0ec72d409d3..7605a2a6d23 100644
--- a/libgo/go/database/sql/fakedb_test.go
+++ b/libgo/go/database/sql/fakedb_test.go
@@ -397,7 +397,7 @@ func (c *fakeConn) ResetSession(ctx context.Context) error {
return nil
}
-var _ validator = (*fakeConn)(nil)
+var _ driver.Validator = (*fakeConn)(nil)
func (c *fakeConn) IsValid() bool {
return !c.isBad()
diff --git a/libgo/go/database/sql/sql.go b/libgo/go/database/sql/sql.go
index a0b7ca8f087..b3d0653f5c6 100644
--- a/libgo/go/database/sql/sql.go
+++ b/libgo/go/database/sql/sql.go
@@ -424,13 +424,15 @@ type DB 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
+ maxIdleCount int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
+ maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
- maxIdleClosed int64 // Total number of connections closed due to idle.
- maxLifetimeClosed int64 // Total number of connections closed due to max free limit.
+ maxIdleClosed int64 // Total number of connections closed due to idle count.
+ maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
+ maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit.
stop func() // stop cancels the connection opener and the session resetter.
}
@@ -464,8 +466,9 @@ type driverConn struct {
// guarded by db.mu
inUse bool
- onPut []func() // code (with db.mu held) run when conn is next returned
- dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked
+ returnedAt time.Time // Time the connection was created or returned.
+ onPut []func() // code (with db.mu held) run when conn is next returned
+ dbmuClosed bool // same as closed, but guarded by db.mu, for removeClosedStmtLocked
}
func (dc *driverConn) releaseConn(err error) {
@@ -500,11 +503,6 @@ func (dc *driverConn) resetSession(ctx context.Context) error {
return nil
}
-// validator was introduced for Go1.15, but backported to Go1.14.
-type validator interface {
- IsValid() bool
-}
-
// validateConnection checks if the connection is valid and can
// still be used. It also marks the session for reset if required.
func (dc *driverConn) validateConnection(needsReset bool) bool {
@@ -514,7 +512,7 @@ func (dc *driverConn) validateConnection(needsReset bool) bool {
if needsReset {
dc.needReset = true
}
- if cv, ok := dc.ci.(validator); ok {
+ if cv, ok := dc.ci.(driver.Validator); ok {
return cv.IsValid()
}
return true
@@ -858,7 +856,7 @@ func (db *DB) Close() error {
const defaultMaxIdleConns = 2
func (db *DB) maxIdleConnsLocked() int {
- n := db.maxIdle
+ n := db.maxIdleCount
switch {
case n == 0:
// TODO(bradfitz): ask driver, if supported, for its default preference
@@ -870,6 +868,14 @@ func (db *DB) maxIdleConnsLocked() int {
}
}
+func (db *DB) shortestIdleTimeLocked() time.Duration {
+ min := db.maxIdleTime
+ if min > db.maxLifetime {
+ min = db.maxLifetime
+ }
+ return min
+}
+
// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
//
@@ -883,14 +889,14 @@ func (db *DB) maxIdleConnsLocked() int {
func (db *DB) SetMaxIdleConns(n int) {
db.mu.Lock()
if n > 0 {
- db.maxIdle = n
+ db.maxIdleCount = n
} else {
// No idle connections.
- db.maxIdle = -1
+ db.maxIdleCount = -1
}
// Make sure maxIdle doesn't exceed maxOpen
if db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen {
- db.maxIdle = db.maxOpen
+ db.maxIdleCount = db.maxOpen
}
var closing []*driverConn
idleCount := len(db.freeConn)
@@ -931,13 +937,13 @@ func (db *DB) SetMaxOpenConns(n int) {
//
// Expired connections may be closed lazily before reuse.
//
-// If d <= 0, connections are reused forever.
+// If d <= 0, connections are not closed due to a connection's age.
func (db *DB) SetConnMaxLifetime(d time.Duration) {
if d < 0 {
d = 0
}
db.mu.Lock()
- // wake cleaner up when lifetime is shortened.
+ // Wake cleaner up when lifetime is shortened.
if d > 0 && d < db.maxLifetime && db.cleanerCh != nil {
select {
case db.cleanerCh <- struct{}{}:
@@ -949,11 +955,34 @@ func (db *DB) SetConnMaxLifetime(d time.Duration) {
db.mu.Unlock()
}
+// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle.
+//
+// Expired connections may be closed lazily before reuse.
+//
+// If d <= 0, connections are not closed due to a connection's idle time.
+func (db *DB) SetConnMaxIdleTime(d time.Duration) {
+ if d < 0 {
+ d = 0
+ }
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ // Wake cleaner up when idle time is shortened.
+ if d > 0 && d < db.maxIdleTime && db.cleanerCh != nil {
+ select {
+ case db.cleanerCh <- struct{}{}:
+ default:
+ }
+ }
+ db.maxIdleTime = d
+ db.startCleanerLocked()
+}
+
// startCleanerLocked starts connectionCleaner if needed.
func (db *DB) startCleanerLocked() {
- if db.maxLifetime > 0 && db.numOpen > 0 && db.cleanerCh == nil {
+ if (db.maxLifetime > 0 || db.maxIdleTime > 0) && db.numOpen > 0 && db.cleanerCh == nil {
db.cleanerCh = make(chan struct{}, 1)
- go db.connectionCleaner(db.maxLifetime)
+ go db.connectionCleaner(db.shortestIdleTimeLocked())
}
}
@@ -972,15 +1001,30 @@ func (db *DB) connectionCleaner(d time.Duration) {
}
db.mu.Lock()
- d = db.maxLifetime
+
+ d = db.shortestIdleTimeLocked()
if db.closed || db.numOpen == 0 || d <= 0 {
db.cleanerCh = nil
db.mu.Unlock()
return
}
- expiredSince := nowFunc().Add(-d)
- var closing []*driverConn
+ closing := db.connectionCleanerRunLocked()
+ db.mu.Unlock()
+ for _, c := range closing {
+ c.Close()
+ }
+
+ if d < minInterval {
+ d = minInterval
+ }
+ t.Reset(d)
+ }
+}
+
+func (db *DB) connectionCleanerRunLocked() (closing []*driverConn) {
+ if db.maxLifetime > 0 {
+ expiredSince := nowFunc().Add(-db.maxLifetime)
for i := 0; i < len(db.freeConn); i++ {
c := db.freeConn[i]
if c.createdAt.Before(expiredSince) {
@@ -993,17 +1037,26 @@ func (db *DB) connectionCleaner(d time.Duration) {
}
}
db.maxLifetimeClosed += int64(len(closing))
- db.mu.Unlock()
-
- for _, c := range closing {
- c.Close()
- }
+ }
- if d < minInterval {
- d = minInterval
+ if db.maxIdleTime > 0 {
+ expiredSince := nowFunc().Add(-db.maxIdleTime)
+ var expiredCount int64
+ for i := 0; i < len(db.freeConn); i++ {
+ c := db.freeConn[i]
+ if db.maxIdleTime > 0 && c.returnedAt.Before(expiredSince) {
+ closing = append(closing, c)
+ expiredCount++
+ last := len(db.freeConn) - 1
+ db.freeConn[i] = db.freeConn[last]
+ db.freeConn[last] = nil
+ db.freeConn = db.freeConn[:last]
+ i--
+ }
}
- t.Reset(d)
+ db.maxIdleTimeClosed += expiredCount
}
+ return
}
// DBStats contains database statistics.
@@ -1019,6 +1072,7 @@ type DBStats struct {
WaitCount int64 // The total number of connections waited for.
WaitDuration time.Duration // The total time blocked waiting for a new connection.
MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns.
+ MaxIdleTimeClosed int64 // The total number of connections closed due to SetConnMaxIdleTime.
MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime.
}
@@ -1039,6 +1093,7 @@ func (db *DB) Stats() DBStats {
WaitCount: db.waitCount,
WaitDuration: time.Duration(wait),
MaxIdleClosed: db.maxIdleClosed,
+ MaxIdleTimeClosed: db.maxIdleTimeClosed,
MaxLifetimeClosed: db.maxLifetimeClosed,
}
return stats
@@ -1099,9 +1154,10 @@ func (db *DB) openNewConnection(ctx context.Context) {
return
}
dc := &driverConn{
- db: db,
- createdAt: nowFunc(),
- ci: ci,
+ db: db,
+ createdAt: nowFunc(),
+ returnedAt: nowFunc(),
+ ci: ci,
}
if db.putConnDBLocked(dc, err) {
db.addDepLocked(dc, dc)
@@ -1152,11 +1208,13 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
copy(db.freeConn, db.freeConn[1:])
db.freeConn = db.freeConn[:numFree-1]
conn.inUse = true
- db.mu.Unlock()
if conn.expired(lifetime) {
+ db.maxLifetimeClosed++
+ db.mu.Unlock()
conn.Close()
return nil, driver.ErrBadConn
}
+ db.mu.Unlock()
// Reset the session if required.
if err := conn.resetSession(ctx); err == driver.ErrBadConn {
@@ -1178,7 +1236,7 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
db.waitCount++
db.mu.Unlock()
- waitStart := time.Now()
+ waitStart := nowFunc()
// Timeout the connection request with the context.
select {
@@ -1212,6 +1270,9 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
// This prioritizes giving a valid connection to a client over the exact connection
// lifetime, which could expire exactly after this point anyway.
if strategy == cachedOrNewConn && ret.err == nil && ret.conn.expired(lifetime) {
+ db.mu.Lock()
+ db.maxLifetimeClosed++
+ db.mu.Unlock()
ret.conn.Close()
return nil, driver.ErrBadConn
}
@@ -1240,10 +1301,11 @@ func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn
}
db.mu.Lock()
dc := &driverConn{
- db: db,
- createdAt: nowFunc(),
- ci: ci,
- inUse: true,
+ db: db,
+ createdAt: nowFunc(),
+ returnedAt: nowFunc(),
+ ci: ci,
+ inUse: true,
}
db.addDepLocked(dc, dc)
db.mu.Unlock()
@@ -1295,12 +1357,14 @@ func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
}
if err != driver.ErrBadConn && dc.expired(db.maxLifetime) {
+ db.maxLifetimeClosed++
err = driver.ErrBadConn
}
if debugGetPut {
db.lastPut[dc] = stack()
}
dc.inUse = false
+ dc.returnedAt = nowFunc()
for _, fn := range dc.onPut {
fn()
@@ -1691,7 +1755,7 @@ func (db *DB) beginDC(ctx context.Context, dc *driverConn, release func(error),
keepConnOnRollback := false
withLock(dc, func() {
_, hasSessionResetter := dc.ci.(driver.SessionResetter)
- _, hasConnectionValidator := dc.ci.(validator)
+ _, hasConnectionValidator := dc.ci.(driver.Validator)
keepConnOnRollback = hasSessionResetter && hasConnectionValidator
txi, err = ctxDriverBegin(ctx, opts, dc.ci)
})
@@ -2729,10 +2793,17 @@ func (rs *Rows) lasterrOrErrLocked(err error) error {
return err
}
+// bypassRowsAwaitDone is only used for testing.
+// If true, it will not close the Rows automatically from the context.
+var bypassRowsAwaitDone = false
+
func (rs *Rows) initContextClose(ctx, txctx context.Context) {
if ctx.Done() == nil && (txctx == nil || txctx.Done() == nil) {
return
}
+ if bypassRowsAwaitDone {
+ return
+ }
ctx, rs.cancel = context.WithCancel(ctx)
go rs.awaitDone(ctx, txctx)
}
@@ -2942,10 +3013,11 @@ func (ci *ColumnType) Nullable() (nullable, ok bool) {
}
// DatabaseTypeName returns the database system name of the column type. If an empty
-// string is returned the driver type name is not supported.
+// string is returned, then the driver type name is not supported.
// Consult your driver documentation for a list of driver data types. Length specifiers
// are not included.
-// Common type include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL", "INT", "BIGINT".
+// Common type names include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL",
+// "INT", and "BIGINT".
func (ci *ColumnType) DatabaseTypeName() string {
return ci.databaseType
}
@@ -3160,6 +3232,14 @@ func (r *Row) Scan(dest ...interface{}) error {
return r.rows.Close()
}
+// Err provides a way for wrapping packages to check for
+// query errors without calling Scan.
+// Err returns the error, if any, that was encountered while running the query.
+// If this error is not nil, this error will also be returned from Scan.
+func (r *Row) Err() error {
+ return r.err
+}
+
// A Result summarizes an executed SQL command.
type Result interface {
// LastInsertId returns the integer generated by the database
diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go
index a9e18004fb4..5727f0d8aa6 100644
--- a/libgo/go/database/sql/sql_test.go
+++ b/libgo/go/database/sql/sql_test.go
@@ -823,6 +823,24 @@ func TestQueryRow(t *testing.T) {
}
}
+func TestRowErr(t *testing.T) {
+ db := newTestDB(t, "people")
+
+ err := db.QueryRowContext(context.Background(), "SELECT|people|bdate|age=?", 3).Err()
+ if err != nil {
+ t.Errorf("Unexpected err = %v; want %v", err, nil)
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ err = db.QueryRowContext(ctx, "SELECT|people|bdate|age=?", 3).Err()
+ exp := "context canceled"
+ if err == nil || !strings.Contains(err.Error(), exp) {
+ t.Errorf("Expected err = %v; got %v", exp, err)
+ }
+}
+
func TestTxRollbackCommitErr(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
@@ -2724,7 +2742,7 @@ func TestManyErrBadConn(t *testing.T) {
}
}
-// Issue 34755: Ensure that a Tx cannot commit after a rollback.
+// Issue 34775: Ensure that a Tx cannot commit after a rollback.
func TestTxCannotCommitAfterRollback(t *testing.T) {
db := newTestDB(t, "tx_status")
defer closeDB(t, db)
@@ -2766,6 +2784,9 @@ func TestTxCannotCommitAfterRollback(t *testing.T) {
// 2. (A) Start a query, (B) begin Tx rollback through a ctx cancel.
// 3. Check if 2.A has committed in Tx (pass) or outside of Tx (fail).
sendQuery := make(chan struct{})
+ // The Tx status is returned through the row results, ensure
+ // that the rows results are not cancelled.
+ bypassRowsAwaitDone = true
hookTxGrabConn = func() {
cancel()
<-sendQuery
@@ -2776,6 +2797,7 @@ func TestTxCannotCommitAfterRollback(t *testing.T) {
defer func() {
hookTxGrabConn = nil
rollbackHook = nil
+ bypassRowsAwaitDone = false
}()
err = tx.QueryRow("SELECT|tx_status|tx_status|").Scan(&txStatus)
@@ -3812,6 +3834,61 @@ func TestStatsMaxIdleClosedTen(t *testing.T) {
}
}
+func TestMaxIdleTime(t *testing.T) {
+ list := []struct {
+ wantMaxIdleTime time.Duration
+ wantIdleClosed int64
+ timeOffset time.Duration
+ }{
+ {time.Nanosecond, 1, 10 * time.Millisecond},
+ {time.Hour, 0, 10 * time.Millisecond},
+ }
+ baseTime := time.Unix(0, 0)
+ defer func() {
+ nowFunc = time.Now
+ }()
+ for _, item := range list {
+ nowFunc = func() time.Time {
+ return baseTime
+ }
+ t.Run(fmt.Sprintf("%v", item.wantMaxIdleTime), func(t *testing.T) {
+ db := newTestDB(t, "people")
+ defer closeDB(t, db)
+
+ db.SetMaxOpenConns(1)
+ db.SetMaxIdleConns(1)
+ db.SetConnMaxIdleTime(item.wantMaxIdleTime)
+ db.SetConnMaxLifetime(0)
+
+ preMaxIdleClosed := db.Stats().MaxIdleTimeClosed
+
+ if err := db.Ping(); err != nil {
+ t.Fatal(err)
+ }
+
+ nowFunc = func() time.Time {
+ return baseTime.Add(item.timeOffset)
+ }
+
+ db.mu.Lock()
+ closing := db.connectionCleanerRunLocked()
+ db.mu.Unlock()
+ for _, c := range closing {
+ c.Close()
+ }
+ if g, w := int64(len(closing)), item.wantIdleClosed; g != w {
+ t.Errorf("got: %d; want %d closed conns", g, w)
+ }
+
+ st := db.Stats()
+ maxIdleClosed := st.MaxIdleTimeClosed - preMaxIdleClosed
+ if g, w := maxIdleClosed, item.wantIdleClosed; g != w {
+ t.Errorf(" got: %d; want %d max idle closed conns", g, w)
+ }
+ })
+ }
+}
+
type nvcDriver struct {
fakeDriver
skipNamedValueCheck bool