Skip to content
Snippets Groups Projects
sql.go 103 KiB
Newer Older
  • Learn to ignore specific revisions
  • // SetMaxOpenConns sets the maximum number of open connections to the database.
    //
    // If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
    // MaxIdleConns, then MaxIdleConns will be reduced to match the new
    
    // MaxOpenConns limit.
    
    //
    // If n <= 0, then there is no limit on the number of open connections.
    // The default is 0 (unlimited).
    func (db *DB) SetMaxOpenConns(n int) {
    	db.mu.Lock()
    	db.maxOpen = n
    	if n < 0 {
    		db.maxOpen = 0
    	}
    	syncMaxIdle := db.maxOpen > 0 && db.maxIdleConnsLocked() > db.maxOpen
    	db.mu.Unlock()
    	if syncMaxIdle {
    		db.SetMaxIdleConns(n)
    	}
    }
    
    
    // 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 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.
    
    	if d > 0 && d < db.maxLifetime && db.cleanerCh != nil {
    		select {
    		case db.cleanerCh <- struct{}{}:
    		default:
    		}
    	}
    	db.maxLifetime = d
    	db.startCleanerLocked()
    	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.maxIdleTime > 0) && db.numOpen > 0 && db.cleanerCh == nil {
    
    		db.cleanerCh = make(chan struct{}, 1)
    
    		go db.connectionCleaner(db.shortestIdleTimeLocked())
    
    	}
    }
    
    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.shortestIdleTimeLocked()
    
    		if db.closed || db.numOpen == 0 || d <= 0 {
    			db.cleanerCh = nil
    			db.mu.Unlock()
    			return
    		}
    
    
    		d, closing := db.connectionCleanerRunLocked(d)
    
    		db.mu.Unlock()
    		for _, c := range closing {
    			c.Close()
    		}
    
    		if d < minInterval {
    			d = minInterval
    		}
    
    
    		if !t.Stop() {
    			select {
    			case <-t.C:
    			default:
    			}
    		}
    
    // connectionCleanerRunLocked removes connections that should be closed from
    // freeConn and returns them along side an updated duration to the next check
    // if a quicker check is required to ensure connections are checked appropriately.
    func (db *DB) connectionCleanerRunLocked(d time.Duration) (time.Duration, []*driverConn) {
    	var idleClosing int64
    	var closing []*driverConn
    	if db.maxIdleTime > 0 {
    		// As freeConn is ordered by returnedAt process
    		// in reverse order to minimise the work needed.
    		idleSince := nowFunc().Add(-db.maxIdleTime)
    		last := len(db.freeConn) - 1
    		for i := last; i >= 0; i-- {
    
    			c := db.freeConn[i]
    
    			if c.returnedAt.Before(idleSince) {
    				i++
    
    				closing = db.freeConn[:i:i]
    
    				db.freeConn = db.freeConn[i:]
    				idleClosing = int64(len(closing))
    				db.maxIdleTimeClosed += idleClosing
    				break
    			}
    		}
    
    		if len(db.freeConn) > 0 {
    			c := db.freeConn[0]
    			if d2 := c.returnedAt.Sub(idleSince); d2 < d {
    				// Ensure idle connections are cleaned up as soon as
    				// possible.
    				d = d2
    
    	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) {
    
    				closing = append(closing, c)
    
    				last := len(db.freeConn) - 1
    
    				// Use slow delete as order is required to ensure
    				// connections are reused least idle time first.
    				copy(db.freeConn[i:], db.freeConn[i+1:])
    
    				db.freeConn[last] = nil
    				db.freeConn = db.freeConn[:last]
    				i--
    
    			} else if d2 := c.createdAt.Sub(expiredSince); d2 < d {
    				// Prevent connections sitting the freeConn when they
    				// have expired by updating our next deadline d.
    				d = d2
    
    		db.maxLifetimeClosed += int64(len(closing)) - idleClosing
    
    // DBStats contains database statistics.
    type DBStats struct {
    
    	MaxOpenConnections int // Maximum number of open connections to the database.
    
    	// Pool Status
    	OpenConnections int // The number of established connections both in use and idle.
    	InUse           int // The number of connections currently in use.
    	Idle            int // The number of idle connections.
    
    	// Counters
    	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.
    
    }
    
    // Stats returns database statistics.
    func (db *DB) Stats() DBStats {
    
    	wait := db.waitDuration.Load()
    
    		MaxOpenConnections: db.maxOpen,
    
    		Idle:            len(db.freeConn),
    
    		InUse:           db.numOpen - len(db.freeConn),
    
    		WaitCount:         db.waitCount,
    		WaitDuration:      time.Duration(wait),
    		MaxIdleClosed:     db.maxIdleClosed,
    
    		MaxIdleTimeClosed: db.maxIdleTimeClosed,
    
    		MaxLifetimeClosed: db.maxLifetimeClosed,
    
    // Assumes db.mu is locked.
    // 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 := db.connRequests.Len()
    
    	if db.maxOpen > 0 {
    
    		numCanOpen := db.maxOpen - db.numOpen
    
    		if numRequests > numCanOpen {
    			numRequests = numCanOpen
    		}
    	}
    	for numRequests > 0 {
    
    		db.numOpen++ // optimistically
    
    		numRequests--
    
    		db.openerCh <- struct{}{}
    	}
    }
    
    
    // Runs in a separate goroutine, opens new connections when requested.
    
    func (db *DB) connectionOpener(ctx context.Context) {
    	for {
    		select {
    		case <-ctx.Done():
    			return
    		case <-db.openerCh:
    			db.openNewConnection(ctx)
    		}
    	}
    }
    
    
    // Open one new connection
    
    func (db *DB) openNewConnection(ctx context.Context) {
    
    	// maybeOpenNewConnections 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.connector.Connect(ctx)
    
    	db.mu.Lock()
    	defer db.mu.Unlock()
    	if db.closed {
    		if err == nil {
    			ci.Close()
    		}
    
    		return
    	}
    	if err != nil {
    
    		db.putConnDBLocked(nil, err)
    
    		db.maybeOpenNewConnections()
    
    		return
    	}
    	dc := &driverConn{
    
    		db:         db,
    		createdAt:  nowFunc(),
    		returnedAt: nowFunc(),
    		ci:         ci,
    
    	if db.putConnDBLocked(dc, err) {
    		db.addDepLocked(dc, dc)
    	} else {
    
    }
    
    // connRequest represents one request for a new connection
    // When there are no idle connections available, DB.conn will create
    // a new connRequest and put it on the db.connRequests list.
    
    type connRequest struct {
    	conn *driverConn
    	err  error
    }
    
    
    var errDBClosed = errors.New("sql: database is closed")
    
    
    // conn returns a newly-opened or cached *driverConn.
    
    func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
    
    		return nil, errDBClosed
    	}
    
    	// Check if the context is expired.
    
    	lifetime := db.maxLifetime
    
    	// Prefer a free connection, if possible.
    
    	last := len(db.freeConn) - 1
    	if strategy == cachedOrNewConn && last >= 0 {
    		// Reuse the lowest idle time connection so we can close
    		// connections which remain idle as soon as possible.
    		conn := db.freeConn[last]
    		db.freeConn = db.freeConn[:last]
    
    		if conn.expired(lifetime) {
    
    			conn.Close()
    			return nil, driver.ErrBadConn
    		}
    
    
    		// Reset the session if required.
    
    		if err := conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) {
    
    	// Out of free connections or we were asked not to use one. If we're not
    
    	// allowed to open any more connections, make a request and wait.
    	if db.maxOpen > 0 && db.numOpen >= db.maxOpen {
    
    		// Make the connRequest channel. It's buffered so that the
    		// connectionOpener doesn't block while waiting for the req to be read.
    
    		req := make(chan connRequest, 1)
    
    		delHandle := db.connRequests.Add(req)
    
    		db.mu.Unlock()
    
    		waitStart := nowFunc()
    
    		// Timeout the connection request with the context.
    		select {
    		case <-ctx.Done():
    
    			// Remove the connection request and ensure no value has been sent
    			// on it after removing.
    			db.mu.Lock()
    
    			deleted := db.connRequests.Delete(delHandle)
    
    			db.waitDuration.Add(int64(time.Since(waitStart)))
    
    			// If we failed to delete it, that means something else
    			// grabbed it and is about to send on it.
    			if !deleted {
    				// TODO(bradfitz): rather than this best effort select, we
    				// should probably start a goroutine to read from req. This best
    				// effort select existed before the change to check 'deleted'.
    				// But if we know for sure it wasn't deleted and a sender is
    				// outstanding, we should probably block on req (in a new
    				// goroutine) to get the connection back.
    				select {
    				default:
    				case ret, ok := <-req:
    					if ok && ret.conn != nil {
    						db.putConn(ret.conn, ret.err, false)
    					}
    
    			return nil, ctx.Err()
    		case ret, ok := <-req:
    
    			db.waitDuration.Add(int64(time.Since(waitStart)))
    
    			if !ok {
    				return nil, errDBClosed
    			}
    
    			// Only check if the connection is expired if the strategy is cachedOrNewConns.
    			// If we require a new connection, just re-use the connection without looking
    			// at the expiry time. If it is expired, it will be checked when it is placed
    			// back into the connection pool.
    			// 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
    			}
    
    			if ret.conn == nil {
    				return nil, ret.err
    			}
    
    
    			// Reset the session if required.
    
    			if err := ret.conn.resetSession(ctx); errors.Is(err, driver.ErrBadConn) {
    
    			return ret.conn, ret.err
    
    	db.numOpen++ // optimistically
    
    	db.mu.Unlock()
    
    	ci, err := db.connector.Connect(ctx)
    
    		db.mu.Lock()
    		db.numOpen-- // correct for earlier optimism
    
    		db.maybeOpenNewConnections()
    
    	db.mu.Lock()
    
    		db:         db,
    		createdAt:  nowFunc(),
    		returnedAt: nowFunc(),
    		ci:         ci,
    		inUse:      true,
    
    // putConnHook is a hook for testing.
    
    var putConnHook func(*DB, *driverConn)
    
    // noteUnusedDriverStatement notes that ds is no longer used and should
    
    // be closed whenever possible (when c is next not in use), unless c is
    // already closed.
    
    func (db *DB) noteUnusedDriverStatement(c *driverConn, ds *driverStmt) {
    
    	db.mu.Lock()
    	defer db.mu.Unlock()
    
    	if c.inUse {
    		c.onPut = append(c.onPut, func() {
    
    		fc := c.finalClosed
    		c.Unlock()
    		if !fc {
    			ds.Close()
    
    	}
    }
    
    // debugGetPut determines whether getConn & putConn calls' stack traces
    // are returned for more verbose crashes.
    const debugGetPut = false
    
    
    // putConn adds a connection to the db's free pool.
    
    Shenghou Ma's avatar
    Shenghou Ma committed
    // err is optionally the last error that occurred on this connection.
    
    func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
    
    	if !errors.Is(err, driver.ErrBadConn) {
    
    		if !dc.validateConnection(resetSession) {
    			err = driver.ErrBadConn
    		}
    	}
    
    			fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc])
    
    		}
    		panic("sql: connection returned that was never out")
    	}
    
    	if !errors.Is(err, driver.ErrBadConn) && dc.expired(db.maxLifetime) {
    
    	dc.returnedAt = nowFunc()
    
    	if errors.Is(err, driver.ErrBadConn) {
    
    		// Don't reuse bad connections.
    
    		// Since the conn is considered bad and is being discarded, treat it
    
    		// as closed. Don't decrement the open count here, finalClose will
    		// take care of that.
    
    		db.maybeOpenNewConnections()
    
    	added := db.putConnDBLocked(dc, nil)
    
    	db.mu.Unlock()
    
    	if !added {
    		dc.Close()
    
    }
    
    // Satisfy a connRequest or put the driverConn in the idle pool and return true
    // or return false.
    // putConnDBLocked will satisfy a connRequest if there is one, or it will
    
    // return the *driverConn to the freeConn list if err == nil and the idle
    // connection limit will not be exceeded.
    
    // If err != nil, the value of dc is ignored.
    // If err == nil, then dc must not equal nil.
    
    Robert Hencke's avatar
    Robert Hencke committed
    // If a connRequest was fulfilled or the *driverConn was placed in the
    
    // freeConn list, then true is returned, otherwise false is returned.
    func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
    
    	if db.maxOpen > 0 && db.numOpen > db.maxOpen {
    		return false
    	}
    
    	if req, ok := db.connRequests.TakeRandom(); ok {
    
    			dc.inUse = true
    
    		}
    		return true
    
    	} else if err == nil && !db.closed {
    		if db.maxIdleConnsLocked() > len(db.freeConn) {
    			db.freeConn = append(db.freeConn, dc)
    			db.startCleanerLocked()
    			return true
    		}
    
    	}
    	return false
    
    // maxBadConnRetries is the number of maximum retries if the driver returns
    
    // driver.ErrBadConn to signal a broken connection before forcing a new
    // connection to be opened.
    const maxBadConnRetries = 2
    
    func (db *DB) retry(fn func(strategy connReuseStrategy) error) error {
    	for i := int64(0); i < maxBadConnRetries; i++ {
    		err := fn(cachedOrNewConn)
    		// retry if err is driver.ErrBadConn
    		if err == nil || !errors.Is(err, driver.ErrBadConn) {
    			return err
    		}
    	}
    
    	return fn(alwaysNewConn)
    }
    
    
    // PrepareContext creates a prepared statement for later queries or executions.
    
    // Multiple queries or executions may be run concurrently from the
    // returned statement.
    
    // The caller must call the statement's [*Stmt.Close] method
    
    // when the statement is no longer needed.
    
    // The provided context is used for the preparation of the statement, not for the
    // execution of the statement.
    
    func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
    
    	var stmt *Stmt
    	var err error
    
    
    	err = db.retry(func(strategy connReuseStrategy) error {
    		stmt, err = db.prepare(ctx, query, strategy)
    		return err
    	})
    
    
    // Prepare creates a prepared statement for later queries or executions.
    // Multiple queries or executions may be run concurrently from the
    // returned statement.
    
    // The caller must call the statement's [*Stmt.Close] method
    
    // when the statement is no longer needed.
    
    // Prepare uses [context.Background] internally; to specify the context, use
    // [DB.PrepareContext].
    
    func (db *DB) Prepare(query string) (*Stmt, error) {
    	return db.PrepareContext(context.Background(), query)
    }
    
    func (db *DB) prepare(ctx context.Context, query string, strategy connReuseStrategy) (*Stmt, error) {
    
    	// TODO: check if db.driver supports an optional
    	// driver.Preparer interface and call that instead, if so,
    	// otherwise we make a prepared statement that's bound
    	// to a connection, and to execute this prepared statement
    	// we either need to use this connection (if it's free), else
    	// get a new connection + re-prepare + execute on that one.
    
    	dc, err := db.conn(ctx, strategy)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return db.prepareDC(ctx, dc, dc.releaseConn, nil, query)
    
    // prepareDC prepares a query on the driverConn and calls release before
    // returning. When cg == nil it implies that a connection pool is used, and
    // when cg != nil only a single driver connection is used.
    func (db *DB) prepareDC(ctx context.Context, dc *driverConn, release func(error), cg stmtConnGrabber, query string) (*Stmt, error) {
    
    	var err error
    	defer func() {
    		release(err)
    	}()
    
    		ds, err = dc.prepareLocked(ctx, cg, query)
    
    	if err != nil {
    		return nil, err
    	}
    
    		db:    db,
    		query: query,
    		cg:    cg,
    		cgds:  ds,
    	}
    
    	// When cg == nil this statement will need to keep track of various
    	// connections they are prepared on and record the stmt dependency on
    	// the DB.
    	if cg == nil {
    		stmt.css = []connStmt{{dc, ds}}
    
    		stmt.lastNumClosed = db.numClosed.Load()
    
    // ExecContext executes a query without returning any rows.
    
    // The args are for any placeholder parameters in the query.
    
    func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error) {
    
    	var res Result
    
    
    	err = db.retry(func(strategy connReuseStrategy) error {
    		res, err = db.exec(ctx, query, args, strategy)
    		return err
    	})
    
    
    // Exec executes a query without returning any rows.
    // The args are for any placeholder parameters in the query.
    
    // Exec uses [context.Background] internally; to specify the context, use
    // [DB.ExecContext].
    
    func (db *DB) Exec(query string, args ...any) (Result, error) {
    
    	return db.ExecContext(context.Background(), query, args...)
    }
    
    
    func (db *DB) exec(ctx context.Context, query string, args []any, strategy connReuseStrategy) (Result, error) {
    
    	dc, err := db.conn(ctx, strategy)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return db.execDC(ctx, dc, dc.releaseConn, query, args)
    }
    
    
    func (db *DB) execDC(ctx context.Context, dc *driverConn, release func(error), query string, args []any) (res Result, err error) {
    
    	execerCtx, ok := dc.ci.(driver.ExecerContext)
    	var execer driver.Execer
    	if !ok {
    		execer, ok = dc.ci.(driver.Execer)
    	}
    	if ok {
    		var nvdargs []driver.NamedValue
    
    		var resi driver.Result
    		withLock(dc, func() {
    
    			nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
    			if err != nil {
    				return
    			}
    
    			resi, err = ctxDriverExec(ctx, execerCtx, execer, query, nvdargs)
    
    		if err != driver.ErrSkip {
    			if err != nil {
    				return nil, err
    			}
    
    			return driverResult{dc, resi}, nil
    
    	var si driver.Stmt
    	withLock(dc, func() {
    
    		si, err = ctxDriverPrepare(ctx, dc.ci, query)
    
    	if err != nil {
    		return nil, err
    	}
    
    	ds := &driverStmt{Locker: dc, si: si}
    	defer ds.Close()
    
    	return resultFromStatement(ctx, dc.ci, ds, args...)
    
    // QueryContext executes a query that returns rows, typically a SELECT.
    
    // The args are for any placeholder parameters in the query.
    
    func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
    
    
    	err = db.retry(func(strategy connReuseStrategy) error {
    		rows, err = db.query(ctx, query, args, strategy)
    		return err
    	})
    
    
    // Query executes a query that returns rows, typically a SELECT.
    // The args are for any placeholder parameters in the query.
    
    // Query uses [context.Background] internally; to specify the context, use
    // [DB.QueryContext].
    
    func (db *DB) Query(query string, args ...any) (*Rows, error) {
    
    	return db.QueryContext(context.Background(), query, args...)
    }
    
    
    func (db *DB) query(ctx context.Context, query string, args []any, strategy connReuseStrategy) (*Rows, error) {
    
    	dc, err := db.conn(ctx, strategy)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return db.queryDC(ctx, nil, dc, dc.releaseConn, query, args)
    
    // queryDC executes a query on the given connection.
    
    // The connection gets released by the releaseConn function.
    
    // The ctx context is from a query method and the txctx context is from an
    // optional transaction context.
    
    func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any) (*Rows, error) {
    
    	queryerCtx, ok := dc.ci.(driver.QueryerContext)
    	var queryer driver.Queryer
    	if !ok {
    		queryer, ok = dc.ci.(driver.Queryer)
    	}
    	if ok {
    
    			nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
    			if err != nil {
    				return
    			}
    
    			rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
    
    		if err != driver.ErrSkip {
    			if err != nil {
    				releaseConn(err)
    				return nil, err
    			}
    
    			// Note: ownership of dc passes to the *Rows, to be freed
    
    			// with releaseConn.
    			rows := &Rows{
    
    				releaseConn: releaseConn,
    				rowsi:       rowsi,
    			}
    
    			rows.initContextClose(ctx, txctx)
    
    	var si driver.Stmt
    	var err error
    	withLock(dc, func() {
    
    		si, err = ctxDriverPrepare(ctx, dc.ci, query)
    
    	if err != nil {
    
    		return nil, err
    	}
    
    	ds := &driverStmt{Locker: dc, si: si}
    
    	rowsi, err := rowsiFromStatement(ctx, dc.ci, ds, args...)
    
    		return nil, err
    	}
    
    	// Note: ownership of ci passes to the *Rows, to be freed
    	// with releaseConn.
    	rows := &Rows{
    
    		releaseConn: releaseConn,
    		rowsi:       rowsi,
    
    	rows.initContextClose(ctx, txctx)
    
    	return rows, nil
    
    // QueryRowContext executes a query that is expected to return at most one row.
    // QueryRowContext always returns a non-nil value. Errors are deferred until
    
    // [Row]'s Scan method is called.
    
    // If the query selects no rows, the [*Row.Scan] will return [ErrNoRows].
    // Otherwise, [*Row.Scan] scans the first selected row and discards
    
    func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row {
    
    	rows, err := db.QueryContext(ctx, query, args...)
    	return &Row{rows: rows, err: err}
    }
    
    
    // QueryRow executes a query that is expected to return at most one row.
    
    Evan Shaw's avatar
    Evan Shaw committed
    // QueryRow always returns a non-nil value. Errors are deferred until
    
    // [Row]'s Scan method is called.
    
    // If the query selects no rows, the [*Row.Scan] will return [ErrNoRows].
    // Otherwise, [*Row.Scan] scans the first selected row and discards
    
    // QueryRow uses [context.Background] internally; to specify the context, use
    // [DB.QueryRowContext].
    
    func (db *DB) QueryRow(query string, args ...any) *Row {
    
    	return db.QueryRowContext(context.Background(), query, args...)
    
    // BeginTx starts a transaction.
    
    // The provided context is used until the transaction is committed or rolled back.
    // If the context is canceled, the sql package will roll back
    
    // the transaction. [Tx.Commit] will return an error if the context provided to
    
    // BeginTx is canceled.
    
    // The provided [TxOptions] is optional and may be nil if defaults should be used.
    
    // If a non-default isolation level is used that the driver doesn't support,
    // an error will be returned.
    func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error) {
    
    	var tx *Tx
    	var err error
    
    
    	err = db.retry(func(strategy connReuseStrategy) error {
    		tx, err = db.begin(ctx, opts, strategy)
    		return err
    	})
    
    
    // Begin starts a transaction. The default isolation level is dependent on
    // the driver.
    
    // Begin uses [context.Background] internally; to specify the context, use
    // [DB.BeginTx].
    
    func (db *DB) Begin() (*Tx, error) {
    
    	return db.BeginTx(context.Background(), nil)
    
    func (db *DB) begin(ctx context.Context, opts *TxOptions, strategy connReuseStrategy) (tx *Tx, err error) {
    
    	dc, err := db.conn(ctx, strategy)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return db.beginDC(ctx, dc, dc.releaseConn, opts)
    }
    
    // beginDC starts a transaction. The provided dc must be valid and ready to use.
    func (db *DB) beginDC(ctx context.Context, dc *driverConn, release func(error), opts *TxOptions) (tx *Tx, err error) {
    
    		_, hasSessionResetter := dc.ci.(driver.SessionResetter)
    		_, hasConnectionValidator := dc.ci.(driver.Validator)
    		keepConnOnRollback = hasSessionResetter && hasConnectionValidator
    
    		txi, err = ctxDriverBegin(ctx, opts, dc.ci)
    
    Naman Gera's avatar
    Naman Gera committed
    	// Schedule the transaction to rollback when the context is canceled.
    
    	// The cancel function in Tx will be called after done is set to true.
    	ctx, cancel := context.WithCancel(ctx)
    	tx = &Tx{
    
    		db:                 db,
    		dc:                 dc,
    		releaseConn:        release,
    		txi:                txi,
    		cancel:             cancel,
    		keepConnOnRollback: keepConnOnRollback,
    		ctx:                ctx,
    
    	return tx, nil
    
    // Driver returns the database's underlying driver.
    
    func (db *DB) Driver() driver.Driver {
    
    // ErrConnDone is returned by any operation that is performed on a connection
    
    // that has already been returned to the connection pool.
    
    var ErrConnDone = errors.New("sql: connection is already closed")
    
    
    // Conn returns a single connection by either opening a new connection
    // or returning an existing connection from the connection pool. Conn will
    // block until either a connection is returned or ctx is canceled.
    // Queries run on the same Conn will be run in the same database session.
    //
    // Every Conn must be returned to the database pool after use by
    
    // calling [Conn.Close].
    
    func (db *DB) Conn(ctx context.Context) (*Conn, error) {
    	var dc *driverConn
    	var err error
    
    
    	err = db.retry(func(strategy connReuseStrategy) error {
    		dc, err = db.conn(ctx, strategy)
    		return err
    	})
    
    
    	if err != nil {
    		return nil, err
    	}
    
    	conn := &Conn{
    		db: db,
    		dc: dc,
    	}
    	return conn, nil
    }
    
    
    // Conn represents a single database connection rather than a pool of database
    
    // connections. Prefer running queries from [DB] unless there is a specific
    
    // need for a continuous single database connection.
    
    // A Conn must call [Conn.Close] to return the connection to the database pool
    
    // and may do so concurrently with a running query.
    //
    
    // After a call to [Conn.Close], all operations on the
    // connection fail with [ErrConnDone].
    
    type Conn struct {
    	db *DB
    
    	// closemu prevents the connection from closing while there
    	// is an active query. It is held for read during queries
    	// and exclusively during close.
    	closemu sync.RWMutex
    
    	// dc is owned until close, at which point
    	// it's returned to the connection pool.
    	dc *driverConn
    
    
    	// done transitions from false to true exactly once, on close.
    
    	// Once done, all operations fail with ErrConnDone.
    
    cui fliter's avatar
    cui fliter committed
    	releaseConnOnce sync.Once
    	// releaseConnCache is a cache of c.closemuRUnlockCondReleaseConn
    
    	// to save allocations in a call to grabConn.
    	releaseConnCache releaseConn
    
    // grabConn takes a context to implement stmtConnGrabber
    // but the context is not used.
    
    func (c *Conn) grabConn(context.Context) (*driverConn, releaseConn, error) {
    
    	c.releaseConnOnce.Do(func() {
    		c.releaseConnCache = c.closemuRUnlockCondReleaseConn
    	})
    
    	return c.dc, c.releaseConnCache, nil
    
    }
    
    // PingContext verifies the connection to the database is still alive.
    func (c *Conn) PingContext(ctx context.Context) error {