exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package sql
|
|
|
|
|
|
|
|
import (
|
2012-01-19 17:04:26 -07:00
|
|
|
"database/sql/driver"
|
2011-11-01 20:04:37 -06:00
|
|
|
"errors"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
"fmt"
|
2011-11-01 20:04:37 -06:00
|
|
|
"io"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
"log"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2013-04-15 15:06:41 -06:00
|
|
|
"testing"
|
2012-01-13 16:45:05 -07:00
|
|
|
"time"
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
var _ = log.Printf
|
|
|
|
|
|
|
|
// fakeDriver is a fake database that implements Go's driver.Driver
|
|
|
|
// interface, just for testing.
|
|
|
|
//
|
|
|
|
// It speaks a query language that's semantically similar to but
|
|
|
|
// syntantically different and simpler than SQL. The syntax is as
|
|
|
|
// follows:
|
|
|
|
//
|
|
|
|
// WIPE
|
|
|
|
// CREATE|<tablename>|<col>=<type>,<col>=<type>,...
|
|
|
|
// where types are: "string", [u]int{8,16,32,64}, "bool"
|
|
|
|
// INSERT|<tablename>|col=val,col2=val2,col3=?
|
|
|
|
// SELECT|<tablename>|projectcol1,projectcol2|filtercol=?,filtercol2=?
|
|
|
|
//
|
2012-07-08 17:16:10 -06:00
|
|
|
// When opening a fakeDriver's database, it starts empty with no
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
// tables. All tables and data are stored in memory only.
|
|
|
|
type fakeDriver struct {
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 15:45:56 -06:00
|
|
|
mu sync.Mutex // guards 3 following fields
|
|
|
|
openCount int // conn opens
|
|
|
|
closeCount int // conn closes
|
|
|
|
dbs map[string]*fakeDB
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type fakeDB struct {
|
|
|
|
name string
|
|
|
|
|
2012-12-14 10:00:33 -07:00
|
|
|
mu sync.Mutex
|
|
|
|
free []*fakeConn
|
|
|
|
tables map[string]*table
|
|
|
|
badConn bool
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type table struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
colname []string
|
|
|
|
coltype []string
|
|
|
|
rows []*row
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *table) columnIndex(name string) int {
|
|
|
|
for n, nname := range t.colname {
|
|
|
|
if name == nname {
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
type row struct {
|
|
|
|
cols []interface{} // must be same size as its table colname + coltype
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *row) clone() *row {
|
|
|
|
nrow := &row{cols: make([]interface{}, len(r.cols))}
|
|
|
|
copy(nrow.cols, r.cols)
|
|
|
|
return nrow
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeConn struct {
|
|
|
|
db *fakeDB // where to return ourselves to
|
|
|
|
|
|
|
|
currTx *fakeTx
|
2012-01-13 16:25:07 -07:00
|
|
|
|
|
|
|
// Stats for tests:
|
|
|
|
mu sync.Mutex
|
|
|
|
stmtsMade int
|
|
|
|
stmtsClosed int
|
2012-03-06 15:10:58 -07:00
|
|
|
numPrepare int
|
2012-12-14 10:00:33 -07:00
|
|
|
bad bool
|
2012-01-13 16:25:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeConn) incrStat(v *int) {
|
|
|
|
c.mu.Lock()
|
|
|
|
*v++
|
|
|
|
c.mu.Unlock()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type fakeTx struct {
|
|
|
|
c *fakeConn
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeStmt struct {
|
|
|
|
c *fakeConn
|
|
|
|
q string // just for debugging
|
|
|
|
|
|
|
|
cmd string
|
|
|
|
table string
|
|
|
|
|
2011-11-20 12:56:49 -07:00
|
|
|
closed bool
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
colName []string // used by CREATE, INSERT, SELECT (selected columns)
|
|
|
|
colType []string // used by CREATE
|
|
|
|
colValue []interface{} // used by INSERT (mix of strings and "?" for bound params)
|
|
|
|
placeholders int // used by INSERT/SELECT: number of ? params
|
|
|
|
|
|
|
|
whereCol []string // used by SELECT (all placeholders)
|
|
|
|
|
|
|
|
placeholderConverter []driver.ValueConverter // used by INSERT
|
|
|
|
}
|
|
|
|
|
|
|
|
var fdriver driver.Driver = &fakeDriver{}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Register("test", fdriver)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Supports dsn forms:
|
|
|
|
// <dbname>
|
2012-12-14 10:00:33 -07:00
|
|
|
// <dbname>;<opts> (only currently supported option is `badConn`,
|
|
|
|
// which causes driver.ErrBadConn to be returned on
|
|
|
|
// every other conn.Begin())
|
2011-11-01 20:04:37 -06:00
|
|
|
func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
parts := strings.Split(dsn, ";")
|
|
|
|
if len(parts) < 1 {
|
2011-11-01 20:04:37 -06:00
|
|
|
return nil, errors.New("fakedb: no database name")
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
name := parts[0]
|
2012-01-10 13:51:27 -07:00
|
|
|
|
|
|
|
db := d.getDB(name)
|
|
|
|
|
|
|
|
d.mu.Lock()
|
|
|
|
d.openCount++
|
|
|
|
d.mu.Unlock()
|
2012-12-14 10:00:33 -07:00
|
|
|
conn := &fakeConn{db: db}
|
|
|
|
|
|
|
|
if len(parts) >= 2 && parts[1] == "badConn" {
|
|
|
|
conn.bad = true
|
|
|
|
}
|
|
|
|
return conn, nil
|
2012-01-10 13:51:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeDriver) getDB(name string) *fakeDB {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
if d.dbs == nil {
|
|
|
|
d.dbs = make(map[string]*fakeDB)
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
db, ok := d.dbs[name]
|
|
|
|
if !ok {
|
|
|
|
db = &fakeDB{name: name}
|
|
|
|
d.dbs[name] = db
|
|
|
|
}
|
2012-01-10 13:51:27 -07:00
|
|
|
return db
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *fakeDB) wipe() {
|
|
|
|
db.mu.Lock()
|
|
|
|
defer db.mu.Unlock()
|
|
|
|
db.tables = nil
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) error {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
db.mu.Lock()
|
|
|
|
defer db.mu.Unlock()
|
|
|
|
if db.tables == nil {
|
|
|
|
db.tables = make(map[string]*table)
|
|
|
|
}
|
|
|
|
if _, exist := db.tables[name]; exist {
|
|
|
|
return fmt.Errorf("table %q already exists", name)
|
|
|
|
}
|
|
|
|
if len(columnNames) != len(columnTypes) {
|
|
|
|
return fmt.Errorf("create table of %q len(names) != len(types): %d vs %d",
|
2011-10-12 20:34:01 -06:00
|
|
|
name, len(columnNames), len(columnTypes))
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
db.tables[name] = &table{colname: columnNames, coltype: columnTypes}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// must be called with db.mu lock held
|
|
|
|
func (db *fakeDB) table(table string) (*table, bool) {
|
|
|
|
if db.tables == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
t, ok := db.tables[table]
|
|
|
|
return t, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *fakeDB) columnType(table, column string) (typ string, ok bool) {
|
|
|
|
db.mu.Lock()
|
|
|
|
defer db.mu.Unlock()
|
|
|
|
t, ok := db.table(table)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for n, cname := range t.colname {
|
|
|
|
if cname == column {
|
|
|
|
return t.coltype[n], true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2012-12-14 10:00:33 -07:00
|
|
|
func (c *fakeConn) isBad() bool {
|
|
|
|
// if not simulating bad conn, do nothing
|
|
|
|
if !c.bad {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// alternate between bad conn and not bad conn
|
|
|
|
c.db.badConn = !c.db.badConn
|
|
|
|
return c.db.badConn
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (c *fakeConn) Begin() (driver.Tx, error) {
|
2012-12-14 10:00:33 -07:00
|
|
|
if c.isBad() {
|
|
|
|
return nil, driver.ErrBadConn
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if c.currTx != nil {
|
2011-11-01 20:04:37 -06:00
|
|
|
return nil, errors.New("already in a transaction")
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
c.currTx = &fakeTx{c: c}
|
|
|
|
return c.currTx, nil
|
|
|
|
}
|
|
|
|
|
2013-03-25 17:50:27 -06:00
|
|
|
var hookPostCloseConn struct {
|
|
|
|
sync.Mutex
|
|
|
|
fn func(*fakeConn, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setHookpostCloseConn(fn func(*fakeConn, error)) {
|
|
|
|
hookPostCloseConn.Lock()
|
|
|
|
defer hookPostCloseConn.Unlock()
|
|
|
|
hookPostCloseConn.fn = fn
|
|
|
|
}
|
|
|
|
|
2013-04-15 15:06:41 -06:00
|
|
|
var testStrictClose *testing.T
|
|
|
|
|
|
|
|
// setStrictFakeConnClose sets the t to Errorf on when fakeConn.Close
|
|
|
|
// fails to close. If nil, the check is disabled.
|
|
|
|
func setStrictFakeConnClose(t *testing.T) {
|
|
|
|
testStrictClose = t
|
|
|
|
}
|
|
|
|
|
2013-03-25 17:50:27 -06:00
|
|
|
func (c *fakeConn) Close() (err error) {
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 15:45:56 -06:00
|
|
|
drv := fdriver.(*fakeDriver)
|
2013-03-25 17:50:27 -06:00
|
|
|
defer func() {
|
2013-04-15 15:06:41 -06:00
|
|
|
if err != nil && testStrictClose != nil {
|
|
|
|
testStrictClose.Errorf("failed to close a test fakeConn: %v", err)
|
|
|
|
}
|
2013-03-25 17:50:27 -06:00
|
|
|
hookPostCloseConn.Lock()
|
|
|
|
fn := hookPostCloseConn.fn
|
|
|
|
hookPostCloseConn.Unlock()
|
|
|
|
if fn != nil {
|
|
|
|
fn(c, err)
|
|
|
|
}
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 15:45:56 -06:00
|
|
|
if err == nil {
|
|
|
|
drv.mu.Lock()
|
|
|
|
drv.closeCount++
|
|
|
|
drv.mu.Unlock()
|
|
|
|
}
|
2013-03-25 17:50:27 -06:00
|
|
|
}()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if c.currTx != nil {
|
2012-03-10 11:00:02 -07:00
|
|
|
return errors.New("can't close fakeConn; in a Transaction")
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
if c.db == nil {
|
2012-03-10 11:00:02 -07:00
|
|
|
return errors.New("can't close fakeConn; already closed")
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
2012-03-10 16:21:44 -07:00
|
|
|
if c.stmtsMade > c.stmtsClosed {
|
|
|
|
return errors.New("can't close; dangling statement(s)")
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
c.db = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func checkSubsetTypes(args []driver.Value) error {
|
2011-11-14 11:48:26 -07:00
|
|
|
for n, arg := range args {
|
|
|
|
switch arg.(type) {
|
2012-01-13 16:45:05 -07:00
|
|
|
case int64, float64, bool, nil, []byte, string, time.Time:
|
2011-11-14 11:48:26 -07:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func (c *fakeConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
2011-11-14 11:48:26 -07:00
|
|
|
// This is an optional interface, but it's implemented here
|
2012-07-08 17:16:10 -06:00
|
|
|
// just to check that all the args are of the proper types.
|
2011-11-14 11:48:26 -07:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:25:39 -07:00
|
|
|
func (c *fakeConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
|
|
|
// This is an optional interface, but it's implemented here
|
|
|
|
// just to check that all the args are 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
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func errf(msg string, args ...interface{}) error {
|
|
|
|
return errors.New("fakedb: " + fmt.Sprintf(msg, args...))
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=?
|
2012-07-08 17:16:10 -06:00
|
|
|
// (note that where columns must always contain ? marks,
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
// just a limitation for fakedb)
|
2011-11-01 20:04:37 -06:00
|
|
|
func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (driver.Stmt, error) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if len(parts) != 3 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("invalid SELECT syntax with %d parts; want 3", len(parts))
|
|
|
|
}
|
|
|
|
stmt.table = parts[0]
|
|
|
|
stmt.colName = strings.Split(parts[1], ",")
|
|
|
|
for n, colspec := range strings.Split(parts[2], ",") {
|
2011-11-20 12:56:49 -07:00
|
|
|
if colspec == "" {
|
|
|
|
continue
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
nameVal := strings.Split(colspec, "=")
|
|
|
|
if len(nameVal) != 2 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("SELECT on table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
|
|
|
|
}
|
|
|
|
column, value := nameVal[0], nameVal[1]
|
|
|
|
_, ok := c.db.columnType(stmt.table, column)
|
|
|
|
if !ok {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column)
|
|
|
|
}
|
|
|
|
if value != "?" {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("SELECT on table %q has pre-bound value for where column %q; need a question mark",
|
|
|
|
stmt.table, column)
|
|
|
|
}
|
|
|
|
stmt.whereCol = append(stmt.whereCol, column)
|
|
|
|
stmt.placeholders++
|
|
|
|
}
|
|
|
|
return stmt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parts are table|col=type,col2=type2
|
2011-11-01 20:04:37 -06:00
|
|
|
func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (driver.Stmt, error) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if len(parts) != 2 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("invalid CREATE syntax with %d parts; want 2", len(parts))
|
|
|
|
}
|
|
|
|
stmt.table = parts[0]
|
|
|
|
for n, colspec := range strings.Split(parts[1], ",") {
|
|
|
|
nameType := strings.Split(colspec, "=")
|
|
|
|
if len(nameType) != 2 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("CREATE table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
|
|
|
|
}
|
|
|
|
stmt.colName = append(stmt.colName, nameType[0])
|
|
|
|
stmt.colType = append(stmt.colType, nameType[1])
|
|
|
|
}
|
|
|
|
return stmt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parts are table|col=?,col2=val
|
2011-11-01 20:04:37 -06:00
|
|
|
func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (driver.Stmt, error) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if len(parts) != 2 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts))
|
|
|
|
}
|
|
|
|
stmt.table = parts[0]
|
|
|
|
for n, colspec := range strings.Split(parts[1], ",") {
|
|
|
|
nameVal := strings.Split(colspec, "=")
|
|
|
|
if len(nameVal) != 2 {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("INSERT table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
|
|
|
|
}
|
|
|
|
column, value := nameVal[0], nameVal[1]
|
|
|
|
ctype, ok := c.db.columnType(stmt.table, column)
|
|
|
|
if !ok {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("INSERT table %q references non-existent column %q", stmt.table, column)
|
|
|
|
}
|
|
|
|
stmt.colName = append(stmt.colName, column)
|
|
|
|
|
|
|
|
if value != "?" {
|
|
|
|
var subsetVal interface{}
|
|
|
|
// Convert to driver subset type
|
|
|
|
switch ctype {
|
|
|
|
case "string":
|
|
|
|
subsetVal = []byte(value)
|
2012-01-12 12:23:33 -07:00
|
|
|
case "blob":
|
|
|
|
subsetVal = []byte(value)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
case "int32":
|
|
|
|
i, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("invalid conversion to int32 from %q", value)
|
|
|
|
}
|
|
|
|
subsetVal = int64(i) // int64 is a subset type, but not int32
|
|
|
|
default:
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype)
|
|
|
|
}
|
|
|
|
stmt.colValue = append(stmt.colValue, subsetVal)
|
|
|
|
} else {
|
|
|
|
stmt.placeholders++
|
|
|
|
stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype))
|
|
|
|
stmt.colValue = append(stmt.colValue, "?")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stmt, nil
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (c *fakeConn) Prepare(query string) (driver.Stmt, error) {
|
2012-03-06 15:10:58 -07:00
|
|
|
c.numPrepare++
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if c.db == nil {
|
|
|
|
panic("nil c.db; conn = " + fmt.Sprintf("%#v", c))
|
|
|
|
}
|
|
|
|
parts := strings.Split(query, "|")
|
|
|
|
if len(parts) < 1 {
|
|
|
|
return nil, errf("empty query")
|
|
|
|
}
|
|
|
|
cmd := parts[0]
|
|
|
|
parts = parts[1:]
|
|
|
|
stmt := &fakeStmt{q: query, c: c, cmd: cmd}
|
2012-01-13 16:25:07 -07:00
|
|
|
c.incrStat(&c.stmtsMade)
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
switch cmd {
|
|
|
|
case "WIPE":
|
|
|
|
// Nothing
|
|
|
|
case "SELECT":
|
|
|
|
return c.prepareSelect(stmt, parts)
|
|
|
|
case "CREATE":
|
|
|
|
return c.prepareCreate(stmt, parts)
|
|
|
|
case "INSERT":
|
|
|
|
return c.prepareInsert(stmt, parts)
|
|
|
|
default:
|
2012-03-10 16:21:44 -07:00
|
|
|
stmt.Close()
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil, errf("unsupported command type %q", cmd)
|
|
|
|
}
|
|
|
|
return stmt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter {
|
2012-05-29 12:09:09 -06:00
|
|
|
if len(s.placeholderConverter) == 0 {
|
|
|
|
return driver.DefaultParameterConverter
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return s.placeholderConverter[idx]
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (s *fakeStmt) Close() error {
|
2013-04-15 15:06:41 -06:00
|
|
|
if s.c == nil {
|
|
|
|
panic("nil conn in fakeStmt.Close")
|
|
|
|
}
|
|
|
|
if s.c.db == nil {
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 15:45:56 -06:00
|
|
|
panic("in fakeStmt.Close, conn's db is nil (already closed)")
|
2013-04-15 15:06:41 -06:00
|
|
|
}
|
2012-01-13 16:25:07 -07:00
|
|
|
if !s.closed {
|
|
|
|
s.c.incrStat(&s.c.stmtsClosed)
|
|
|
|
s.closed = true
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-11-20 12:56:49 -07:00
|
|
|
var errClosed = errors.New("fakedb: statement has been closed")
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) {
|
2011-11-20 12:56:49 -07:00
|
|
|
if s.closed {
|
|
|
|
return nil, errClosed
|
|
|
|
}
|
2011-11-14 11:48:26 -07:00
|
|
|
err := checkSubsetTypes(args)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
db := s.c.db
|
|
|
|
switch s.cmd {
|
|
|
|
case "WIPE":
|
|
|
|
db.wipe()
|
2012-02-19 20:25:28 -07:00
|
|
|
return driver.ResultNoRows, nil
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
case "CREATE":
|
|
|
|
if err := db.createTable(s.table, s.colName, s.colType); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2012-02-19 20:25:28 -07:00
|
|
|
return driver.ResultNoRows, nil
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
case "INSERT":
|
|
|
|
return s.execInsert(args)
|
|
|
|
}
|
|
|
|
fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s)
|
|
|
|
return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd)
|
|
|
|
}
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func (s *fakeStmt) execInsert(args []driver.Value) (driver.Result, error) {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
db := s.c.db
|
|
|
|
if len(args) != s.placeholders {
|
|
|
|
panic("error in pkg db; should only get here if size is correct")
|
|
|
|
}
|
|
|
|
db.mu.Lock()
|
|
|
|
t, ok := db.table(s.table)
|
|
|
|
db.mu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
cols := make([]interface{}, len(t.colname))
|
|
|
|
argPos := 0
|
|
|
|
for n, colname := range s.colName {
|
|
|
|
colidx := t.columnIndex(colname)
|
|
|
|
if colidx == -1 {
|
|
|
|
return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname)
|
|
|
|
}
|
|
|
|
var val interface{}
|
|
|
|
if strvalue, ok := s.colValue[n].(string); ok && strvalue == "?" {
|
|
|
|
val = args[argPos]
|
|
|
|
argPos++
|
|
|
|
} else {
|
|
|
|
val = s.colValue[n]
|
|
|
|
}
|
|
|
|
cols[colidx] = val
|
|
|
|
}
|
|
|
|
|
|
|
|
t.rows = append(t.rows, &row{cols: cols})
|
|
|
|
return driver.RowsAffected(1), nil
|
|
|
|
}
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) {
|
2011-11-20 12:56:49 -07:00
|
|
|
if s.closed {
|
|
|
|
return nil, errClosed
|
|
|
|
}
|
2011-11-14 11:48:26 -07:00
|
|
|
err := checkSubsetTypes(args)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
db := s.c.db
|
|
|
|
if len(args) != s.placeholders {
|
|
|
|
panic("error in pkg db; should only get here if size is correct")
|
|
|
|
}
|
|
|
|
|
|
|
|
db.mu.Lock()
|
|
|
|
t, ok := db.table(s.table)
|
|
|
|
db.mu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
|
|
|
|
}
|
database/sql: fix driver Conn refcounting with prepared statements
The refcounting of driver Conns was completedly busted and
would leak (be held open forever) with any reasonable
load. This was a significant regression from Go 1.0.
The core of this patch is removing one line:
s.db.addDep(dc, s)
A database conn (dc) is a resource that be re-created any time
(but cached for speed) should not be held open forever with a
dependency refcount just because the Stmt (s) is alive (which
typically last for long periods of time, like forever).
The meat of the patch is new tests. In fixing the real issue,
a lot of tests then failed due to the fakedb_test.go's paranoia
about closing a fakeConn while it has open fakeStmts on it. I
could've ignored that, but that's been a problem in the past for
other bugs.
Instead, I now track per-Conn open statements and close them
when the the conn closes. The proper way to do this would've
been making *driverStmt a finalCloser and using the dep mechanism,
but it was much more invasive. Added a TODO instead.
I'd like to give a way for drivers to opt-out of caring about
driver.Stmt closes before a driver.Conn close, but that's a TODO
for the future, and that TODO is added in this CL.
I know this is very late for Go 1.1, but database/sql is
currently nearly useless without this.
I'd like to believe all these database/sql bugs in the past
release cycle are the result of increased usage, number of
drivers, and good feedback from increasingly-capable Go
developers, and not the result of me sucking. It's also hard
with all the real drivers being out-of-tree, so I'm having to
add more and more hooks to fakedb_test.go to simulate things
which real drivers end up doing.
Fixes #5323
R=golang-dev, snaury, gwenn.kahz, google, r
CC=golang-dev
https://golang.org/cl/8836045
2013-04-25 15:45:56 -06:00
|
|
|
|
|
|
|
if s.table == "magicquery" {
|
|
|
|
if len(s.whereCol) == 2 && s.whereCol[0] == "op" && s.whereCol[1] == "millis" {
|
|
|
|
if args[0] == "sleep" {
|
|
|
|
time.Sleep(time.Duration(args[1].(int64)) * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
colIdx := make(map[string]int) // select column name -> column index in table
|
|
|
|
for _, name := range s.colName {
|
|
|
|
idx := t.columnIndex(name)
|
|
|
|
if idx == -1 {
|
|
|
|
return nil, fmt.Errorf("fakedb: unknown column name %q", name)
|
|
|
|
}
|
|
|
|
colIdx[name] = idx
|
|
|
|
}
|
|
|
|
|
|
|
|
mrows := []*row{}
|
|
|
|
rows:
|
|
|
|
for _, trow := range t.rows {
|
|
|
|
// Process the where clause, skipping non-match rows. This is lazy
|
|
|
|
// and just uses fmt.Sprintf("%v") to test equality. Good enough
|
|
|
|
// for test code.
|
|
|
|
for widx, wcol := range s.whereCol {
|
|
|
|
idx := t.columnIndex(wcol)
|
|
|
|
if idx == -1 {
|
|
|
|
return nil, fmt.Errorf("db: invalid where clause column %q", wcol)
|
|
|
|
}
|
|
|
|
tcol := trow.cols[idx]
|
|
|
|
if bs, ok := tcol.([]byte); ok {
|
|
|
|
// lazy hack to avoid sprintf %v on a []byte
|
|
|
|
tcol = string(bs)
|
|
|
|
}
|
|
|
|
if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", args[widx]) {
|
|
|
|
continue rows
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mrow := &row{cols: make([]interface{}, len(s.colName))}
|
|
|
|
for seli, name := range s.colName {
|
|
|
|
mrow.cols[seli] = trow.cols[colIdx[name]]
|
|
|
|
}
|
|
|
|
mrows = append(mrows, mrow)
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor := &rowsCursor{
|
|
|
|
pos: -1,
|
|
|
|
rows: mrows,
|
|
|
|
cols: s.colName,
|
|
|
|
}
|
|
|
|
return cursor, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeStmt) NumInput() int {
|
|
|
|
return s.placeholders
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (tx *fakeTx) Commit() error {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
tx.c.currTx = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (tx *fakeTx) Rollback() error {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
tx.c.currTx = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type rowsCursor struct {
|
|
|
|
cols []string
|
|
|
|
pos int
|
|
|
|
rows []*row
|
|
|
|
closed bool
|
2012-01-12 12:23:33 -07:00
|
|
|
|
|
|
|
// a clone of slices to give out to clients, indexed by the
|
|
|
|
// the original slice's first byte address. we clone them
|
|
|
|
// just so we're able to corrupt them on close.
|
|
|
|
bytesClone map[*byte][]byte
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (rc *rowsCursor) Close() error {
|
2012-01-12 12:23:33 -07:00
|
|
|
if !rc.closed {
|
|
|
|
for _, bs := range rc.bytesClone {
|
|
|
|
bs[0] = 255 // first byte corrupted
|
|
|
|
}
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
rc.closed = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *rowsCursor) Columns() []string {
|
|
|
|
return rc.cols
|
|
|
|
}
|
|
|
|
|
2012-02-19 20:25:28 -07:00
|
|
|
func (rc *rowsCursor) Next(dest []driver.Value) error {
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
if rc.closed {
|
2011-11-01 20:04:37 -06:00
|
|
|
return errors.New("fakedb: cursor is closed")
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
rc.pos++
|
|
|
|
if rc.pos >= len(rc.rows) {
|
2011-11-01 20:04:37 -06:00
|
|
|
return io.EOF // per interface spec
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
for i, v := range rc.rows[rc.pos].cols {
|
|
|
|
// TODO(bradfitz): convert to subset types? naah, I
|
|
|
|
// think the subset types should only be input to
|
2011-11-02 12:46:04 -06:00
|
|
|
// driver, but the sql package should be able to handle
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
// a wider range of types coming out of drivers. all
|
|
|
|
// for ease of drivers, and to prevent drivers from
|
|
|
|
// messing up conversions or doing them differently.
|
|
|
|
dest[i] = v
|
2012-01-12 12:23:33 -07:00
|
|
|
|
|
|
|
if bs, ok := v.([]byte); ok {
|
|
|
|
if rc.bytesClone == nil {
|
|
|
|
rc.bytesClone = make(map[*byte][]byte)
|
|
|
|
}
|
|
|
|
clone, ok := rc.bytesClone[&bs[0]]
|
|
|
|
if !ok {
|
|
|
|
clone = make([]byte, len(bs))
|
|
|
|
copy(clone, bs)
|
|
|
|
rc.bytesClone[&bs[0]] = clone
|
|
|
|
}
|
|
|
|
dest[i] = clone
|
|
|
|
}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-05-29 12:09:09 -06:00
|
|
|
// fakeDriverString is like driver.String, but indirects pointers like
|
|
|
|
// DefaultValueConverter.
|
|
|
|
//
|
|
|
|
// This could be surprising behavior to retroactively apply to
|
|
|
|
// driver.String now that Go1 is out, but this is convenient for
|
|
|
|
// our TestPointerParamsAndScans.
|
|
|
|
//
|
|
|
|
type fakeDriverString struct{}
|
|
|
|
|
|
|
|
func (fakeDriverString) ConvertValue(v interface{}) (driver.Value, error) {
|
|
|
|
switch c := v.(type) {
|
|
|
|
case string, []byte:
|
|
|
|
return v, nil
|
|
|
|
case *string:
|
|
|
|
if c == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return *c, nil
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%v", v), nil
|
|
|
|
}
|
|
|
|
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
func converterForType(typ string) driver.ValueConverter {
|
|
|
|
switch typ {
|
|
|
|
case "bool":
|
|
|
|
return driver.Bool
|
2012-01-25 18:47:32 -07:00
|
|
|
case "nullbool":
|
2012-02-02 16:12:25 -07:00
|
|
|
return driver.Null{Converter: driver.Bool}
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
case "int32":
|
|
|
|
return driver.Int32
|
|
|
|
case "string":
|
2012-05-29 12:09:09 -06:00
|
|
|
return driver.NotNull{Converter: fakeDriverString{}}
|
2012-01-19 10:27:45 -07:00
|
|
|
case "nullstring":
|
2012-05-29 12:09:09 -06:00
|
|
|
return driver.Null{Converter: fakeDriverString{}}
|
2012-01-25 18:47:32 -07:00
|
|
|
case "int64":
|
|
|
|
// TODO(coopernurse): add type-specific converter
|
2012-02-02 16:12:25 -07:00
|
|
|
return driver.NotNull{Converter: driver.DefaultParameterConverter}
|
2012-01-25 18:47:32 -07:00
|
|
|
case "nullint64":
|
|
|
|
// TODO(coopernurse): add type-specific converter
|
2012-02-02 16:12:25 -07:00
|
|
|
return driver.Null{Converter: driver.DefaultParameterConverter}
|
2012-01-25 18:47:32 -07:00
|
|
|
case "float64":
|
|
|
|
// TODO(coopernurse): add type-specific converter
|
2012-02-02 16:12:25 -07:00
|
|
|
return driver.NotNull{Converter: driver.DefaultParameterConverter}
|
2012-01-25 18:47:32 -07:00
|
|
|
case "nullfloat64":
|
|
|
|
// TODO(coopernurse): add type-specific converter
|
2012-02-02 16:12:25 -07:00
|
|
|
return driver.Null{Converter: driver.DefaultParameterConverter}
|
2012-01-13 16:45:05 -07:00
|
|
|
case "datetime":
|
|
|
|
return driver.DefaultParameterConverter
|
exp/sql{,/driver}: new database packages
R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
2011-09-29 17:12:21 -06:00
|
|
|
}
|
|
|
|
panic("invalid fakedb column type of " + typ)
|
|
|
|
}
|