mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
database/sql: add driver.ResetSessioner and add pool support
A single database connection ususally maps to a single session. A connection pool is logically also a session pool. Most sessions have a way to reset the session state which is desirable to prevent one bad query from poisoning another later query with temp table name conflicts or other persistent session resources. It also lets drivers provide users with better error messages from queryies when the underlying transport or query method fails. Internally the driver connection should now be marked as bad, but return the actual connection. When ResetSession is called on the connection it should return driver.ErrBadConn to remove it from the connection pool. Previously drivers had to choose between meaningful error messages or poisoning the connection pool. Lastly update TestPoolExhaustOnCancel from relying on a WAIT query fixing a flaky timeout issue exposed by this change. Fixes #22049 Fixes #20807 Change-Id: Idffa1a7ca9ccfe633257c4a3ae299b864f46c5b6 Reviewed-on: https://go-review.googlesource.com/67630 Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
532714829e
commit
2620ac3aea
4 changed files with 214 additions and 25 deletions
|
|
@ -55,6 +55,22 @@ type fakeDriver struct {
|
|||
dbs map[string]*fakeDB
|
||||
}
|
||||
|
||||
type fakeConnector struct {
|
||||
name string
|
||||
|
||||
waiter func(context.Context)
|
||||
}
|
||||
|
||||
func (c *fakeConnector) Connect(context.Context) (driver.Conn, error) {
|
||||
conn, err := fdriver.Open(c.name)
|
||||
conn.(*fakeConn).waiter = c.waiter
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (c *fakeConnector) Driver() driver.Driver {
|
||||
return fdriver
|
||||
}
|
||||
|
||||
type fakeDB struct {
|
||||
name string
|
||||
|
||||
|
|
@ -107,6 +123,16 @@ type fakeConn struct {
|
|||
// bad connection tests; see isBad()
|
||||
bad bool
|
||||
stickyBad bool
|
||||
|
||||
skipDirtySession bool // tests that use Conn should set this to true.
|
||||
|
||||
// dirtySession tests ResetSession, true if a query has executed
|
||||
// until ResetSession is called.
|
||||
dirtySession bool
|
||||
|
||||
// The waiter is called before each query. May be used in place of the "WAIT"
|
||||
// directive.
|
||||
waiter func(context.Context)
|
||||
}
|
||||
|
||||
func (c *fakeConn) touchMem() {
|
||||
|
|
@ -298,6 +324,9 @@ func (c *fakeConn) isBad() bool {
|
|||
if c.stickyBad {
|
||||
return true
|
||||
} else if c.bad {
|
||||
if c.db == nil {
|
||||
return false
|
||||
}
|
||||
// alternate between bad conn and not bad conn
|
||||
c.db.badConn = !c.db.badConn
|
||||
return c.db.badConn
|
||||
|
|
@ -306,6 +335,21 @@ func (c *fakeConn) isBad() bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *fakeConn) isDirtyAndMark() bool {
|
||||
if c.skipDirtySession {
|
||||
return false
|
||||
}
|
||||
if c.currTx != nil {
|
||||
c.dirtySession = true
|
||||
return false
|
||||
}
|
||||
if c.dirtySession {
|
||||
return true
|
||||
}
|
||||
c.dirtySession = true
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *fakeConn) Begin() (driver.Tx, error) {
|
||||
if c.isBad() {
|
||||
return nil, driver.ErrBadConn
|
||||
|
|
@ -337,6 +381,14 @@ func setStrictFakeConnClose(t *testing.T) {
|
|||
testStrictClose = t
|
||||
}
|
||||
|
||||
func (c *fakeConn) ResetSession(ctx context.Context) error {
|
||||
c.dirtySession = false
|
||||
if c.isBad() {
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeConn) Close() (err error) {
|
||||
drv := fdriver.(*fakeDriver)
|
||||
defer func() {
|
||||
|
|
@ -572,6 +624,10 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
|
|||
stmt.cmd = cmd
|
||||
parts = parts[1:]
|
||||
|
||||
if c.waiter != nil {
|
||||
c.waiter(ctx)
|
||||
}
|
||||
|
||||
if stmt.wait > 0 {
|
||||
wait := time.NewTimer(stmt.wait)
|
||||
select {
|
||||
|
|
@ -662,6 +718,9 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d
|
|||
if s.c.stickyBad || (hookExecBadConn != nil && hookExecBadConn()) {
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if s.c.isDirtyAndMark() {
|
||||
return nil, errors.New("session is dirty")
|
||||
}
|
||||
|
||||
err := checkSubsetTypes(s.c.db.allowAny, args)
|
||||
if err != nil {
|
||||
|
|
@ -774,6 +833,9 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (
|
|||
if s.c.stickyBad || (hookQueryBadConn != nil && hookQueryBadConn()) {
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if s.c.isDirtyAndMark() {
|
||||
return nil, errors.New("session is dirty")
|
||||
}
|
||||
|
||||
err := checkSubsetTypes(s.c.db.allowAny, args)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue