// 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. Load more
// 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 failures var 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 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 } 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=type2 func (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=val func (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 connections var 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")