866 lines · 20.7 KB · Go
RawDownload
// 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 (	"database/sql/driver"	"errors"	"fmt"	"io"	"log"	"sort"	"strconv"	"strings"	"sync"	"testing"	"time") 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// syntactically 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=?//// When opening a fakeDriver's database, it starts empty with no// tables.  All tables and data are stored in memory only.type fakeDriver struct {	mu         sync.Mutex // guards 3 following fields	openCount  int        // conn opens	closeCount int        // conn closes	waitCh     chan struct{}	waitingCh  chan struct{}	dbs        map[string]*fakeDB} type fakeDB struct {	name string 	mu      sync.Mutex	free    []*fakeConn	tables  map[string]*table	badConn bool} 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 	// Stats for tests:	mu          sync.Mutex	stmtsMade   int	stmtsClosed int	numPrepare  int 	// bad connection tests; see isBad()	bad       bool	stickyBad bool} func (c *fakeConn) incrStat(v *int) {	c.mu.Lock()	*v++	c.mu.Unlock()} type fakeTx struct {	c *fakeConn} type fakeStmt struct {	c *fakeConn	q string // just for debugging 	cmd   string	table string 	closed bool 	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)} func contains(list []string, y string) bool {	for _, x := range list {		if x == y {			return true		}	}	return false} type Dummy struct {	driver.Driver} func TestDrivers(t *testing.T) {	unregisterAllDrivers()	Register("test", fdriver)	Register("invalid", Dummy{})	all := Drivers()	if len(all) < 2 || !sort.StringsAreSorted(all) || !contains(all, "test") || !contains(all, "invalid") {		t.Fatalf("Drivers = %v, want sorted list with at least [invalid, test]", all)	}} // hook to simulate connection failuresvar hookOpenErr struct {	sync.Mutex	fn func() error} func setHookOpenErr(fn func() error) {	hookOpenErr.Lock()	defer hookOpenErr.Unlock()	hookOpenErr.fn = fn} // Supports dsn forms://    <dbname>//    <dbname>;<opts>  (only currently supported option is `badConn`,//                      which causes driver.ErrBadConn to be returned on//                      every other conn.Begin())func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {	hookOpenErr.Lock()	fn := hookOpenErr.fn	hookOpenErr.Unlock()	if fn != nil {		if err := fn(); err != nil {			return nil, err		}	}	parts := strings.Split(dsn, ";")	if len(parts) < 1 {		return nil, errors.New("fakedb: no database name")	}	name := parts[0] 	db := d.getDB(name) 	d.mu.Lock()	d.openCount++	d.mu.Unlock()	conn := &fakeConn{db: db} 	if len(parts) >= 2 && parts[1] == "badConn" {		conn.bad = true	}	if d.waitCh != nil {		d.waitingCh <- struct{}{}		<-d.waitCh		d.waitCh = nil		d.waitingCh = nil	}	return conn, nil} func (d *fakeDriver) getDB(name string) *fakeDB {	d.mu.Lock()	defer d.mu.Unlock()	if d.dbs == nil {		d.dbs = make(map[string]*fakeDB)	}	db, ok := d.dbs[name]	if !ok {		db = &fakeDB{name: name}		d.dbs[name] = db	}	return db} func (db *fakeDB) wipe() {	db.mu.Lock()	defer db.mu.Unlock()	db.tables = nil} func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) error {	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",			name, len(columnNames), len(columnTypes))	}	db.tables[name] = &table{colname: columnNames, coltype: columnTypes}	return nil} // must be called with db.mu lock heldfunc (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} func (c *fakeConn) isBad() bool {	if c.stickyBad {		return true	} else if c.bad {		// alternate between bad conn and not bad conn		c.db.badConn = !c.db.badConn		return c.db.badConn	} else {		return false	}} func (c *fakeConn) Begin() (driver.Tx, error) {	if c.isBad() {		return nil, driver.ErrBadConn	}	if c.currTx != nil {		return nil, errors.New("already in a transaction")	}	c.currTx = &fakeTx{c: c}	return c.currTx, nil} var hookPostCloseConn struct {	sync.Mutex	fn func(*fakeConn, error)} func setHookpostCloseConn(fn func(*fakeConn, error)) {	hookPostCloseConn.Lock()	defer hookPostCloseConn.Unlock()	hookPostCloseConn.fn = fn} 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} func (c *fakeConn) Close() (err error) {	drv := fdriver.(*fakeDriver)	defer func() {		if err != nil && testStrictClose != nil {			testStrictClose.Errorf("failed to close a test fakeConn: %v", err)		}		hookPostCloseConn.Lock()		fn := hookPostCloseConn.fn		hookPostCloseConn.Unlock()		if fn != nil {			fn(c, err)		}		if err == nil {			drv.mu.Lock()			drv.closeCount++			drv.mu.Unlock()		}	}()	if c.currTx != nil {		return errors.New("can't close fakeConn; in a Transaction")	}	if c.db == nil {		return errors.New("can't close fakeConn; already closed")	}	if c.stmtsMade > c.stmtsClosed {		return errors.New("can't close; dangling statement(s)")	}	c.db = nil	return nil} func checkSubsetTypes(args []driver.Value) error {	for n, arg := range args {		switch arg.(type) {		case int64, float64, bool, nil, []byte, string, time.Time:		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 []driver.Value) (driver.Result, 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} 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} func errf(msg string, args ...interface{}) error {	return errors.New("fakedb: " + fmt.Sprintf(msg, args...))} // parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=?// (note that where columns must always contain ? marks,//  just a limitation for fakedb)func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (driver.Stmt, error) {	if len(parts) != 3 {		stmt.Close()		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], ",") {		if colspec == "" {			continue		}		nameVal := strings.Split(colspec, "=")		if len(nameVal) != 2 {			stmt.Close()			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 {			stmt.Close()			return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column)		}		if value != "?" {			stmt.Close()			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=type2func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (driver.Stmt, error) {	if len(parts) != 2 {		stmt.Close()		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 {			stmt.Close()			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=valfunc (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (driver.Stmt, error) {	if len(parts) != 2 {		stmt.Close()		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 {			stmt.Close()			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 {			stmt.Close()			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)			case "blob":				subsetVal = []byte(value)			case "int32":				i, err := strconv.Atoi(value)				if err != nil {					stmt.Close()					return nil, errf("invalid conversion to int32 from %q", value)				}				subsetVal = int64(i) // int64 is a subset type, but not int32			default:				stmt.Close()				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} // hook to simulate broken connectionsvar hookPrepareBadConn func() bool func (c *fakeConn) Prepare(query string) (driver.Stmt, error) {	c.numPrepare++	if c.db == nil {		panic("nil c.db; conn = " + fmt.Sprintf("%#v", c))	} 	if c.stickyBad || (hookPrepareBadConn != nil && hookPrepareBadConn()) {		return nil, driver.ErrBadConn	} 	parts := strings.Split(query, "|")	if len(parts) < 1 {		return nil, errf("empty query")

Lines 1–500 of 866.