Commit 0a8005c7 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

sql: add DB.Close, fix bugs, remove Execer on Driver (only Conn)

R=rsc
CC=golang-dev
https://golang.org/cl/5372099
parent 869aabbd
...@@ -14,6 +14,21 @@ import ( ...@@ -14,6 +14,21 @@ import (
"strconv" "strconv"
) )
// subsetTypeArgs takes a slice of arguments from callers of the sql
// package and converts them into a slice of the driver package's
// "subset types".
func subsetTypeArgs(args []interface{}) ([]interface{}, error) {
out := make([]interface{}, len(args))
for n, arg := range args {
var err error
out[n], err = driver.DefaultParameterConverter.ConvertValue(arg)
if err != nil {
return nil, fmt.Errorf("sql: converting argument #%d's type: %v", n+1, err)
}
}
return out, nil
}
// convertAssign copies to dest the value in src, converting it if possible. // convertAssign copies to dest the value in src, converting it if possible.
// An error is returned if the copy would result in loss of information. // An error is returned if the copy would result in loss of information.
// dest should be a pointer type. // dest should be a pointer type.
......
...@@ -36,19 +36,22 @@ type Driver interface { ...@@ -36,19 +36,22 @@ type Driver interface {
Open(name string) (Conn, error) Open(name string) (Conn, error)
} }
// Execer is an optional interface that may be implemented by a Driver // ErrSkip may be returned by some optional interfaces' methods to
// or a Conn. // indicate at runtime that the fast path is unavailable and the sql
// // package should continue as if the optional interface was not
// If a Driver does not implement Execer, the sql package's DB.Exec // implemented. ErrSkip is only supported where explicitly
// method first obtains a free connection from its free pool or from // documented.
// the driver's Open method. Execer should only be implemented by var ErrSkip = errors.New("driver: skip fast-path; continue as if unimplemented")
// drivers that can provide a more efficient implementation.
// Execer is an optional interface that may be implemented by a Conn.
// //
// If a Conn does not implement Execer, the db package's DB.Exec will // If a Conn does not implement Execer, the db package's DB.Exec will
// first prepare a query, execute the statement, and then close the // first prepare a query, execute the statement, and then close the
// statement. // statement.
// //
// All arguments are of a subset type as defined in the package docs. // All arguments are of a subset type as defined in the package docs.
//
// Exec may return ErrSkip.
type Execer interface { type Execer interface {
Exec(query string, args []interface{}) (Result, error) Exec(query string, args []interface{}) (Result, error)
} }
......
...@@ -195,6 +195,29 @@ func (c *fakeConn) Close() error { ...@@ -195,6 +195,29 @@ func (c *fakeConn) Close() error {
return nil return nil
} }
func checkSubsetTypes(args []interface{}) error {
for n, arg := range args {
switch arg.(type) {
case int64, float64, bool, nil, []byte, string:
default:
return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg)
}
}
return nil
}
func (c *fakeConn) Exec(query string, args []interface{}) (driver.Result, error) {
// This is an optional interface, but it's implemented here
// just to check that all the args of of the proper types.
// ErrSkip is returned so the caller acts as if we didn't
// implement this at all.
err := checkSubsetTypes(args)
if err != nil {
return nil, err
}
return nil, driver.ErrSkip
}
func errf(msg string, args ...interface{}) error { func errf(msg string, args ...interface{}) error {
return errors.New("fakedb: " + fmt.Sprintf(msg, args...)) return errors.New("fakedb: " + fmt.Sprintf(msg, args...))
} }
...@@ -323,6 +346,11 @@ func (s *fakeStmt) Close() error { ...@@ -323,6 +346,11 @@ func (s *fakeStmt) Close() error {
} }
func (s *fakeStmt) Exec(args []interface{}) (driver.Result, error) { func (s *fakeStmt) Exec(args []interface{}) (driver.Result, error) {
err := checkSubsetTypes(args)
if err != nil {
return nil, err
}
db := s.c.db db := s.c.db
switch s.cmd { switch s.cmd {
case "WIPE": case "WIPE":
...@@ -377,6 +405,11 @@ func (s *fakeStmt) execInsert(args []interface{}) (driver.Result, error) { ...@@ -377,6 +405,11 @@ func (s *fakeStmt) execInsert(args []interface{}) (driver.Result, error) {
} }
func (s *fakeStmt) Query(args []interface{}) (driver.Rows, error) { func (s *fakeStmt) Query(args []interface{}) (driver.Rows, error) {
err := checkSubsetTypes(args)
if err != nil {
return nil, err
}
db := s.c.db db := s.c.db
if len(args) != s.placeholders { if len(args) != s.placeholders {
panic("error in pkg db; should only get here if size is correct") panic("error in pkg db; should only get here if size is correct")
......
...@@ -88,8 +88,9 @@ type DB struct { ...@@ -88,8 +88,9 @@ type DB struct {
driver driver.Driver driver driver.Driver
dsn string dsn string
mu sync.Mutex mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn freeConn []driver.Conn
closed bool
} }
// Open opens a database specified by its database driver name and a // Open opens a database specified by its database driver name and a
...@@ -106,6 +107,22 @@ func Open(driverName, dataSourceName string) (*DB, error) { ...@@ -106,6 +107,22 @@ func Open(driverName, dataSourceName string) (*DB, error) {
return &DB{driver: driver, dsn: dataSourceName}, nil return &DB{driver: driver, dsn: dataSourceName}, nil
} }
// Close closes the database, releasing any open resources.
func (db *DB) Close() error {
db.mu.Lock()
defer db.mu.Unlock()
var err error
for _, c := range db.freeConn {
err1 := c.Close()
if err1 != nil {
err = err1
}
}
db.freeConn = nil
db.closed = true
return err
}
func (db *DB) maxIdleConns() int { func (db *DB) maxIdleConns() int {
const defaultMaxIdleConns = 2 const defaultMaxIdleConns = 2
// TODO(bradfitz): ask driver, if supported, for its default preference // TODO(bradfitz): ask driver, if supported, for its default preference
...@@ -116,6 +133,9 @@ func (db *DB) maxIdleConns() int { ...@@ -116,6 +133,9 @@ func (db *DB) maxIdleConns() int {
// conn returns a newly-opened or cached driver.Conn // conn returns a newly-opened or cached driver.Conn
func (db *DB) conn() (driver.Conn, error) { func (db *DB) conn() (driver.Conn, error) {
db.mu.Lock() db.mu.Lock()
if db.closed {
return nil, errors.New("sql: database is closed")
}
if n := len(db.freeConn); n > 0 { if n := len(db.freeConn); n > 0 {
conn := db.freeConn[n-1] conn := db.freeConn[n-1]
db.freeConn = db.freeConn[:n-1] db.freeConn = db.freeConn[:n-1]
...@@ -140,11 +160,13 @@ func (db *DB) connIfFree(wanted driver.Conn) (conn driver.Conn, ok bool) { ...@@ -140,11 +160,13 @@ func (db *DB) connIfFree(wanted driver.Conn) (conn driver.Conn, ok bool) {
} }
func (db *DB) putConn(c driver.Conn) { func (db *DB) putConn(c driver.Conn) {
if n := len(db.freeConn); n < db.maxIdleConns() { db.mu.Lock()
defer db.mu.Unlock()
if n := len(db.freeConn); !db.closed && n < db.maxIdleConns() {
db.freeConn = append(db.freeConn, c) db.freeConn = append(db.freeConn, c)
return return
} }
db.closeConn(c) db.closeConn(c) // TODO(bradfitz): release lock before calling this?
} }
func (db *DB) closeConn(c driver.Conn) { func (db *DB) closeConn(c driver.Conn) {
...@@ -180,17 +202,11 @@ func (db *DB) Prepare(query string) (*Stmt, error) { ...@@ -180,17 +202,11 @@ func (db *DB) Prepare(query string) (*Stmt, error) {
// Exec executes a query without returning any rows. // Exec executes a query without returning any rows.
func (db *DB) Exec(query string, args ...interface{}) (Result, error) { func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
// Optional fast path, if the driver implements driver.Execer. sargs, err := subsetTypeArgs(args)
if execer, ok := db.driver.(driver.Execer); ok {
resi, err := execer.Exec(query, args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return result{resi}, nil
}
// If the driver does not implement driver.Execer, we need
// a connection.
ci, err := db.conn() ci, err := db.conn()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -198,19 +214,22 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) { ...@@ -198,19 +214,22 @@ func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
defer db.putConn(ci) defer db.putConn(ci)
if execer, ok := ci.(driver.Execer); ok { if execer, ok := ci.(driver.Execer); ok {
resi, err := execer.Exec(query, args) resi, err := execer.Exec(query, sargs)
if err != driver.ErrSkip {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return result{resi}, nil return result{resi}, nil
} }
}
sti, err := ci.Prepare(query) sti, err := ci.Prepare(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer sti.Close() defer sti.Close()
resi, err := sti.Exec(args)
resi, err := sti.Exec(sargs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -386,7 +405,13 @@ func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) { ...@@ -386,7 +405,13 @@ func (tx *Tx) Exec(query string, args ...interface{}) (Result, error) {
return nil, err return nil, err
} }
defer sti.Close() defer sti.Close()
resi, err := sti.Exec(args)
sargs, err := subsetTypeArgs(args)
if err != nil {
return nil, err
}
resi, err := sti.Exec(sargs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -548,7 +573,11 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { ...@@ -548,7 +573,11 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
if len(args) != si.NumInput() { if len(args) != si.NumInput() {
return nil, fmt.Errorf("db: statement expects %d inputs; got %d", si.NumInput(), len(args)) return nil, fmt.Errorf("db: statement expects %d inputs; got %d", si.NumInput(), len(args))
} }
rowsi, err := si.Query(args) sargs, err := subsetTypeArgs(args)
if err != nil {
return nil, err
}
rowsi, err := si.Query(sargs)
if err != nil { if err != nil {
s.db.putConn(ci) s.db.putConn(ci)
return nil, err return nil, err
......
...@@ -34,8 +34,16 @@ func exec(t *testing.T, db *DB, query string, args ...interface{}) { ...@@ -34,8 +34,16 @@ func exec(t *testing.T, db *DB, query string, args ...interface{}) {
} }
} }
func closeDB(t *testing.T, db *DB) {
err := db.Close()
if err != nil {
t.Fatalf("error closing DB: %v", err)
}
}
func TestQuery(t *testing.T) { func TestQuery(t *testing.T) {
db := newTestDB(t, "people") db := newTestDB(t, "people")
defer closeDB(t, db)
var name string var name string
var age int var age int
...@@ -69,6 +77,7 @@ func TestQuery(t *testing.T) { ...@@ -69,6 +77,7 @@ func TestQuery(t *testing.T) {
func TestStatementQueryRow(t *testing.T) { func TestStatementQueryRow(t *testing.T) {
db := newTestDB(t, "people") db := newTestDB(t, "people")
defer closeDB(t, db)
stmt, err := db.Prepare("SELECT|people|age|name=?") stmt, err := db.Prepare("SELECT|people|age|name=?")
if err != nil { if err != nil {
t.Fatalf("Prepare: %v", err) t.Fatalf("Prepare: %v", err)
...@@ -94,6 +103,7 @@ func TestStatementQueryRow(t *testing.T) { ...@@ -94,6 +103,7 @@ func TestStatementQueryRow(t *testing.T) {
// just a test of fakedb itself // just a test of fakedb itself
func TestBogusPreboundParameters(t *testing.T) { func TestBogusPreboundParameters(t *testing.T) {
db := newTestDB(t, "foo") db := newTestDB(t, "foo")
defer closeDB(t, db)
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool") exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
_, err := db.Prepare("INSERT|t1|name=?,age=bogusconversion") _, err := db.Prepare("INSERT|t1|name=?,age=bogusconversion")
if err == nil { if err == nil {
...@@ -106,6 +116,7 @@ func TestBogusPreboundParameters(t *testing.T) { ...@@ -106,6 +116,7 @@ func TestBogusPreboundParameters(t *testing.T) {
func TestDb(t *testing.T) { func TestDb(t *testing.T) {
db := newTestDB(t, "foo") db := newTestDB(t, "foo")
defer closeDB(t, db)
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool") exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
stmt, err := db.Prepare("INSERT|t1|name=?,age=?") stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
if err != nil { if err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment