github.com/bgentry/go@v0.0.0-20150121062915-6cf5a733d54d/src/database/sql/fakedb_test.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sql 6 7 import ( 8 "database/sql/driver" 9 "errors" 10 "fmt" 11 "io" 12 "log" 13 "sort" 14 "strconv" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 ) 20 21 var _ = log.Printf 22 23 // fakeDriver is a fake database that implements Go's driver.Driver 24 // interface, just for testing. 25 // 26 // It speaks a query language that's semantically similar to but 27 // syntactically different and simpler than SQL. The syntax is as 28 // follows: 29 // 30 // WIPE 31 // CREATE|<tablename>|<col>=<type>,<col>=<type>,... 32 // where types are: "string", [u]int{8,16,32,64}, "bool" 33 // INSERT|<tablename>|col=val,col2=val2,col3=? 34 // SELECT|<tablename>|projectcol1,projectcol2|filtercol=?,filtercol2=? 35 // 36 // When opening a fakeDriver's database, it starts empty with no 37 // tables. All tables and data are stored in memory only. 38 type fakeDriver struct { 39 mu sync.Mutex // guards 3 following fields 40 openCount int // conn opens 41 closeCount int // conn closes 42 waitCh chan struct{} 43 waitingCh chan struct{} 44 dbs map[string]*fakeDB 45 } 46 47 type fakeDB struct { 48 name string 49 50 mu sync.Mutex 51 free []*fakeConn 52 tables map[string]*table 53 badConn bool 54 } 55 56 type table struct { 57 mu sync.Mutex 58 colname []string 59 coltype []string 60 rows []*row 61 } 62 63 func (t *table) columnIndex(name string) int { 64 for n, nname := range t.colname { 65 if name == nname { 66 return n 67 } 68 } 69 return -1 70 } 71 72 type row struct { 73 cols []interface{} // must be same size as its table colname + coltype 74 } 75 76 func (r *row) clone() *row { 77 nrow := &row{cols: make([]interface{}, len(r.cols))} 78 copy(nrow.cols, r.cols) 79 return nrow 80 } 81 82 type fakeConn struct { 83 db *fakeDB // where to return ourselves to 84 85 currTx *fakeTx 86 87 // Stats for tests: 88 mu sync.Mutex 89 stmtsMade int 90 stmtsClosed int 91 numPrepare int 92 bad bool 93 } 94 95 func (c *fakeConn) incrStat(v *int) { 96 c.mu.Lock() 97 *v++ 98 c.mu.Unlock() 99 } 100 101 type fakeTx struct { 102 c *fakeConn 103 } 104 105 type fakeStmt struct { 106 c *fakeConn 107 q string // just for debugging 108 109 cmd string 110 table string 111 112 closed bool 113 114 colName []string // used by CREATE, INSERT, SELECT (selected columns) 115 colType []string // used by CREATE 116 colValue []interface{} // used by INSERT (mix of strings and "?" for bound params) 117 placeholders int // used by INSERT/SELECT: number of ? params 118 119 whereCol []string // used by SELECT (all placeholders) 120 121 placeholderConverter []driver.ValueConverter // used by INSERT 122 } 123 124 var fdriver driver.Driver = &fakeDriver{} 125 126 func init() { 127 Register("test", fdriver) 128 } 129 130 func contains(list []string, y string) bool { 131 for _, x := range list { 132 if x == y { 133 return true 134 } 135 } 136 return false 137 } 138 139 type Dummy struct { 140 driver.Driver 141 } 142 143 func TestDrivers(t *testing.T) { 144 unregisterAllDrivers() 145 Register("test", fdriver) 146 Register("invalid", Dummy{}) 147 all := Drivers() 148 if len(all) < 2 || !sort.StringsAreSorted(all) || !contains(all, "test") || !contains(all, "invalid") { 149 t.Fatalf("Drivers = %v, want sorted list with at least [invalid, test]", all) 150 } 151 } 152 153 // Supports dsn forms: 154 // <dbname> 155 // <dbname>;<opts> (only currently supported option is `badConn`, 156 // which causes driver.ErrBadConn to be returned on 157 // every other conn.Begin()) 158 func (d *fakeDriver) Open(dsn string) (driver.Conn, error) { 159 parts := strings.Split(dsn, ";") 160 if len(parts) < 1 { 161 return nil, errors.New("fakedb: no database name") 162 } 163 name := parts[0] 164 165 db := d.getDB(name) 166 167 d.mu.Lock() 168 d.openCount++ 169 d.mu.Unlock() 170 conn := &fakeConn{db: db} 171 172 if len(parts) >= 2 && parts[1] == "badConn" { 173 conn.bad = true 174 } 175 if d.waitCh != nil { 176 d.waitingCh <- struct{}{} 177 <-d.waitCh 178 d.waitCh = nil 179 d.waitingCh = nil 180 } 181 return conn, nil 182 } 183 184 func (d *fakeDriver) getDB(name string) *fakeDB { 185 d.mu.Lock() 186 defer d.mu.Unlock() 187 if d.dbs == nil { 188 d.dbs = make(map[string]*fakeDB) 189 } 190 db, ok := d.dbs[name] 191 if !ok { 192 db = &fakeDB{name: name} 193 d.dbs[name] = db 194 } 195 return db 196 } 197 198 func (db *fakeDB) wipe() { 199 db.mu.Lock() 200 defer db.mu.Unlock() 201 db.tables = nil 202 } 203 204 func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) error { 205 db.mu.Lock() 206 defer db.mu.Unlock() 207 if db.tables == nil { 208 db.tables = make(map[string]*table) 209 } 210 if _, exist := db.tables[name]; exist { 211 return fmt.Errorf("table %q already exists", name) 212 } 213 if len(columnNames) != len(columnTypes) { 214 return fmt.Errorf("create table of %q len(names) != len(types): %d vs %d", 215 name, len(columnNames), len(columnTypes)) 216 } 217 db.tables[name] = &table{colname: columnNames, coltype: columnTypes} 218 return nil 219 } 220 221 // must be called with db.mu lock held 222 func (db *fakeDB) table(table string) (*table, bool) { 223 if db.tables == nil { 224 return nil, false 225 } 226 t, ok := db.tables[table] 227 return t, ok 228 } 229 230 func (db *fakeDB) columnType(table, column string) (typ string, ok bool) { 231 db.mu.Lock() 232 defer db.mu.Unlock() 233 t, ok := db.table(table) 234 if !ok { 235 return 236 } 237 for n, cname := range t.colname { 238 if cname == column { 239 return t.coltype[n], true 240 } 241 } 242 return "", false 243 } 244 245 func (c *fakeConn) isBad() bool { 246 // if not simulating bad conn, do nothing 247 if !c.bad { 248 return false 249 } 250 // alternate between bad conn and not bad conn 251 c.db.badConn = !c.db.badConn 252 return c.db.badConn 253 } 254 255 func (c *fakeConn) Begin() (driver.Tx, error) { 256 if c.isBad() { 257 return nil, driver.ErrBadConn 258 } 259 if c.currTx != nil { 260 return nil, errors.New("already in a transaction") 261 } 262 c.currTx = &fakeTx{c: c} 263 return c.currTx, nil 264 } 265 266 var hookPostCloseConn struct { 267 sync.Mutex 268 fn func(*fakeConn, error) 269 } 270 271 func setHookpostCloseConn(fn func(*fakeConn, error)) { 272 hookPostCloseConn.Lock() 273 defer hookPostCloseConn.Unlock() 274 hookPostCloseConn.fn = fn 275 } 276 277 var testStrictClose *testing.T 278 279 // setStrictFakeConnClose sets the t to Errorf on when fakeConn.Close 280 // fails to close. If nil, the check is disabled. 281 func setStrictFakeConnClose(t *testing.T) { 282 testStrictClose = t 283 } 284 285 func (c *fakeConn) Close() (err error) { 286 drv := fdriver.(*fakeDriver) 287 defer func() { 288 if err != nil && testStrictClose != nil { 289 testStrictClose.Errorf("failed to close a test fakeConn: %v", err) 290 } 291 hookPostCloseConn.Lock() 292 fn := hookPostCloseConn.fn 293 hookPostCloseConn.Unlock() 294 if fn != nil { 295 fn(c, err) 296 } 297 if err == nil { 298 drv.mu.Lock() 299 drv.closeCount++ 300 drv.mu.Unlock() 301 } 302 }() 303 if c.currTx != nil { 304 return errors.New("can't close fakeConn; in a Transaction") 305 } 306 if c.db == nil { 307 return errors.New("can't close fakeConn; already closed") 308 } 309 if c.stmtsMade > c.stmtsClosed { 310 return errors.New("can't close; dangling statement(s)") 311 } 312 c.db = nil 313 return nil 314 } 315 316 func checkSubsetTypes(args []driver.Value) error { 317 for n, arg := range args { 318 switch arg.(type) { 319 case int64, float64, bool, nil, []byte, string, time.Time: 320 default: 321 return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg) 322 } 323 } 324 return nil 325 } 326 327 func (c *fakeConn) Exec(query string, args []driver.Value) (driver.Result, error) { 328 // This is an optional interface, but it's implemented here 329 // just to check that all the args are of the proper types. 330 // ErrSkip is returned so the caller acts as if we didn't 331 // implement this at all. 332 err := checkSubsetTypes(args) 333 if err != nil { 334 return nil, err 335 } 336 return nil, driver.ErrSkip 337 } 338 339 func (c *fakeConn) Query(query string, args []driver.Value) (driver.Rows, error) { 340 // This is an optional interface, but it's implemented here 341 // just to check that all the args are of the proper types. 342 // ErrSkip is returned so the caller acts as if we didn't 343 // implement this at all. 344 err := checkSubsetTypes(args) 345 if err != nil { 346 return nil, err 347 } 348 return nil, driver.ErrSkip 349 } 350 351 func errf(msg string, args ...interface{}) error { 352 return errors.New("fakedb: " + fmt.Sprintf(msg, args...)) 353 } 354 355 // parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=? 356 // (note that where columns must always contain ? marks, 357 // just a limitation for fakedb) 358 func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (driver.Stmt, error) { 359 if len(parts) != 3 { 360 stmt.Close() 361 return nil, errf("invalid SELECT syntax with %d parts; want 3", len(parts)) 362 } 363 stmt.table = parts[0] 364 stmt.colName = strings.Split(parts[1], ",") 365 for n, colspec := range strings.Split(parts[2], ",") { 366 if colspec == "" { 367 continue 368 } 369 nameVal := strings.Split(colspec, "=") 370 if len(nameVal) != 2 { 371 stmt.Close() 372 return nil, errf("SELECT on table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) 373 } 374 column, value := nameVal[0], nameVal[1] 375 _, ok := c.db.columnType(stmt.table, column) 376 if !ok { 377 stmt.Close() 378 return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column) 379 } 380 if value != "?" { 381 stmt.Close() 382 return nil, errf("SELECT on table %q has pre-bound value for where column %q; need a question mark", 383 stmt.table, column) 384 } 385 stmt.whereCol = append(stmt.whereCol, column) 386 stmt.placeholders++ 387 } 388 return stmt, nil 389 } 390 391 // parts are table|col=type,col2=type2 392 func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (driver.Stmt, error) { 393 if len(parts) != 2 { 394 stmt.Close() 395 return nil, errf("invalid CREATE syntax with %d parts; want 2", len(parts)) 396 } 397 stmt.table = parts[0] 398 for n, colspec := range strings.Split(parts[1], ",") { 399 nameType := strings.Split(colspec, "=") 400 if len(nameType) != 2 { 401 stmt.Close() 402 return nil, errf("CREATE table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) 403 } 404 stmt.colName = append(stmt.colName, nameType[0]) 405 stmt.colType = append(stmt.colType, nameType[1]) 406 } 407 return stmt, nil 408 } 409 410 // parts are table|col=?,col2=val 411 func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (driver.Stmt, error) { 412 if len(parts) != 2 { 413 stmt.Close() 414 return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts)) 415 } 416 stmt.table = parts[0] 417 for n, colspec := range strings.Split(parts[1], ",") { 418 nameVal := strings.Split(colspec, "=") 419 if len(nameVal) != 2 { 420 stmt.Close() 421 return nil, errf("INSERT table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) 422 } 423 column, value := nameVal[0], nameVal[1] 424 ctype, ok := c.db.columnType(stmt.table, column) 425 if !ok { 426 stmt.Close() 427 return nil, errf("INSERT table %q references non-existent column %q", stmt.table, column) 428 } 429 stmt.colName = append(stmt.colName, column) 430 431 if value != "?" { 432 var subsetVal interface{} 433 // Convert to driver subset type 434 switch ctype { 435 case "string": 436 subsetVal = []byte(value) 437 case "blob": 438 subsetVal = []byte(value) 439 case "int32": 440 i, err := strconv.Atoi(value) 441 if err != nil { 442 stmt.Close() 443 return nil, errf("invalid conversion to int32 from %q", value) 444 } 445 subsetVal = int64(i) // int64 is a subset type, but not int32 446 default: 447 stmt.Close() 448 return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype) 449 } 450 stmt.colValue = append(stmt.colValue, subsetVal) 451 } else { 452 stmt.placeholders++ 453 stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype)) 454 stmt.colValue = append(stmt.colValue, "?") 455 } 456 } 457 return stmt, nil 458 } 459 460 // hook to simulate broken connections 461 var hookPrepareBadConn func() bool 462 463 func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { 464 c.numPrepare++ 465 if c.db == nil { 466 panic("nil c.db; conn = " + fmt.Sprintf("%#v", c)) 467 } 468 469 if hookPrepareBadConn != nil && hookPrepareBadConn() { 470 return nil, driver.ErrBadConn 471 } 472 473 parts := strings.Split(query, "|") 474 if len(parts) < 1 { 475 return nil, errf("empty query") 476 } 477 cmd := parts[0] 478 parts = parts[1:] 479 stmt := &fakeStmt{q: query, c: c, cmd: cmd} 480 c.incrStat(&c.stmtsMade) 481 switch cmd { 482 case "WIPE": 483 // Nothing 484 case "SELECT": 485 return c.prepareSelect(stmt, parts) 486 case "CREATE": 487 return c.prepareCreate(stmt, parts) 488 case "INSERT": 489 return c.prepareInsert(stmt, parts) 490 case "NOSERT": 491 // Do all the prep-work like for an INSERT but don't actually insert the row. 492 // Used for some of the concurrent tests. 493 return c.prepareInsert(stmt, parts) 494 default: 495 stmt.Close() 496 return nil, errf("unsupported command type %q", cmd) 497 } 498 return stmt, nil 499 } 500 501 func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter { 502 if len(s.placeholderConverter) == 0 { 503 return driver.DefaultParameterConverter 504 } 505 return s.placeholderConverter[idx] 506 } 507 508 func (s *fakeStmt) Close() error { 509 if s.c == nil { 510 panic("nil conn in fakeStmt.Close") 511 } 512 if s.c.db == nil { 513 panic("in fakeStmt.Close, conn's db is nil (already closed)") 514 } 515 if !s.closed { 516 s.c.incrStat(&s.c.stmtsClosed) 517 s.closed = true 518 } 519 return nil 520 } 521 522 var errClosed = errors.New("fakedb: statement has been closed") 523 524 // hook to simulate broken connections 525 var hookExecBadConn func() bool 526 527 func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) { 528 if s.closed { 529 return nil, errClosed 530 } 531 532 if hookExecBadConn != nil && hookExecBadConn() { 533 return nil, driver.ErrBadConn 534 } 535 536 err := checkSubsetTypes(args) 537 if err != nil { 538 return nil, err 539 } 540 541 db := s.c.db 542 switch s.cmd { 543 case "WIPE": 544 db.wipe() 545 return driver.ResultNoRows, nil 546 case "CREATE": 547 if err := db.createTable(s.table, s.colName, s.colType); err != nil { 548 return nil, err 549 } 550 return driver.ResultNoRows, nil 551 case "INSERT": 552 return s.execInsert(args, true) 553 case "NOSERT": 554 // Do all the prep-work like for an INSERT but don't actually insert the row. 555 // Used for some of the concurrent tests. 556 return s.execInsert(args, false) 557 } 558 fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s) 559 return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd) 560 } 561 562 // When doInsert is true, add the row to the table. 563 // When doInsert is false do prep-work and error checking, but don't 564 // actually add the row to the table. 565 func (s *fakeStmt) execInsert(args []driver.Value, doInsert bool) (driver.Result, error) { 566 db := s.c.db 567 if len(args) != s.placeholders { 568 panic("error in pkg db; should only get here if size is correct") 569 } 570 db.mu.Lock() 571 t, ok := db.table(s.table) 572 db.mu.Unlock() 573 if !ok { 574 return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table) 575 } 576 577 t.mu.Lock() 578 defer t.mu.Unlock() 579 580 var cols []interface{} 581 if doInsert { 582 cols = make([]interface{}, len(t.colname)) 583 } 584 argPos := 0 585 for n, colname := range s.colName { 586 colidx := t.columnIndex(colname) 587 if colidx == -1 { 588 return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname) 589 } 590 var val interface{} 591 if strvalue, ok := s.colValue[n].(string); ok && strvalue == "?" { 592 val = args[argPos] 593 argPos++ 594 } else { 595 val = s.colValue[n] 596 } 597 if doInsert { 598 cols[colidx] = val 599 } 600 } 601 602 if doInsert { 603 t.rows = append(t.rows, &row{cols: cols}) 604 } 605 return driver.RowsAffected(1), nil 606 } 607 608 // hook to simulate broken connections 609 var hookQueryBadConn func() bool 610 611 func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) { 612 if s.closed { 613 return nil, errClosed 614 } 615 616 if hookQueryBadConn != nil && hookQueryBadConn() { 617 return nil, driver.ErrBadConn 618 } 619 620 err := checkSubsetTypes(args) 621 if err != nil { 622 return nil, err 623 } 624 625 db := s.c.db 626 if len(args) != s.placeholders { 627 panic("error in pkg db; should only get here if size is correct") 628 } 629 630 db.mu.Lock() 631 t, ok := db.table(s.table) 632 db.mu.Unlock() 633 if !ok { 634 return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table) 635 } 636 637 if s.table == "magicquery" { 638 if len(s.whereCol) == 2 && s.whereCol[0] == "op" && s.whereCol[1] == "millis" { 639 if args[0] == "sleep" { 640 time.Sleep(time.Duration(args[1].(int64)) * time.Millisecond) 641 } 642 } 643 } 644 645 t.mu.Lock() 646 defer t.mu.Unlock() 647 648 colIdx := make(map[string]int) // select column name -> column index in table 649 for _, name := range s.colName { 650 idx := t.columnIndex(name) 651 if idx == -1 { 652 return nil, fmt.Errorf("fakedb: unknown column name %q", name) 653 } 654 colIdx[name] = idx 655 } 656 657 mrows := []*row{} 658 rows: 659 for _, trow := range t.rows { 660 // Process the where clause, skipping non-match rows. This is lazy 661 // and just uses fmt.Sprintf("%v") to test equality. Good enough 662 // for test code. 663 for widx, wcol := range s.whereCol { 664 idx := t.columnIndex(wcol) 665 if idx == -1 { 666 return nil, fmt.Errorf("db: invalid where clause column %q", wcol) 667 } 668 tcol := trow.cols[idx] 669 if bs, ok := tcol.([]byte); ok { 670 // lazy hack to avoid sprintf %v on a []byte 671 tcol = string(bs) 672 } 673 if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", args[widx]) { 674 continue rows 675 } 676 } 677 mrow := &row{cols: make([]interface{}, len(s.colName))} 678 for seli, name := range s.colName { 679 mrow.cols[seli] = trow.cols[colIdx[name]] 680 } 681 mrows = append(mrows, mrow) 682 } 683 684 cursor := &rowsCursor{ 685 pos: -1, 686 rows: mrows, 687 cols: s.colName, 688 errPos: -1, 689 } 690 return cursor, nil 691 } 692 693 func (s *fakeStmt) NumInput() int { 694 return s.placeholders 695 } 696 697 func (tx *fakeTx) Commit() error { 698 tx.c.currTx = nil 699 return nil 700 } 701 702 func (tx *fakeTx) Rollback() error { 703 tx.c.currTx = nil 704 return nil 705 } 706 707 type rowsCursor struct { 708 cols []string 709 pos int 710 rows []*row 711 closed bool 712 713 // errPos and err are for making Next return early with error. 714 errPos int 715 err error 716 717 // a clone of slices to give out to clients, indexed by the 718 // the original slice's first byte address. we clone them 719 // just so we're able to corrupt them on close. 720 bytesClone map[*byte][]byte 721 } 722 723 func (rc *rowsCursor) Close() error { 724 if !rc.closed { 725 for _, bs := range rc.bytesClone { 726 bs[0] = 255 // first byte corrupted 727 } 728 } 729 rc.closed = true 730 return nil 731 } 732 733 func (rc *rowsCursor) Columns() []string { 734 return rc.cols 735 } 736 737 var rowsCursorNextHook func(dest []driver.Value) error 738 739 func (rc *rowsCursor) Next(dest []driver.Value) error { 740 if rowsCursorNextHook != nil { 741 return rowsCursorNextHook(dest) 742 } 743 744 if rc.closed { 745 return errors.New("fakedb: cursor is closed") 746 } 747 rc.pos++ 748 if rc.pos == rc.errPos { 749 return rc.err 750 } 751 if rc.pos >= len(rc.rows) { 752 return io.EOF // per interface spec 753 } 754 for i, v := range rc.rows[rc.pos].cols { 755 // TODO(bradfitz): convert to subset types? naah, I 756 // think the subset types should only be input to 757 // driver, but the sql package should be able to handle 758 // a wider range of types coming out of drivers. all 759 // for ease of drivers, and to prevent drivers from 760 // messing up conversions or doing them differently. 761 dest[i] = v 762 763 if bs, ok := v.([]byte); ok { 764 if rc.bytesClone == nil { 765 rc.bytesClone = make(map[*byte][]byte) 766 } 767 clone, ok := rc.bytesClone[&bs[0]] 768 if !ok { 769 clone = make([]byte, len(bs)) 770 copy(clone, bs) 771 rc.bytesClone[&bs[0]] = clone 772 } 773 dest[i] = clone 774 } 775 } 776 return nil 777 } 778 779 // fakeDriverString is like driver.String, but indirects pointers like 780 // DefaultValueConverter. 781 // 782 // This could be surprising behavior to retroactively apply to 783 // driver.String now that Go1 is out, but this is convenient for 784 // our TestPointerParamsAndScans. 785 // 786 type fakeDriverString struct{} 787 788 func (fakeDriverString) ConvertValue(v interface{}) (driver.Value, error) { 789 switch c := v.(type) { 790 case string, []byte: 791 return v, nil 792 case *string: 793 if c == nil { 794 return nil, nil 795 } 796 return *c, nil 797 } 798 return fmt.Sprintf("%v", v), nil 799 } 800 801 func converterForType(typ string) driver.ValueConverter { 802 switch typ { 803 case "bool": 804 return driver.Bool 805 case "nullbool": 806 return driver.Null{Converter: driver.Bool} 807 case "int32": 808 return driver.Int32 809 case "string": 810 return driver.NotNull{Converter: fakeDriverString{}} 811 case "nullstring": 812 return driver.Null{Converter: fakeDriverString{}} 813 case "int64": 814 // TODO(coopernurse): add type-specific converter 815 return driver.NotNull{Converter: driver.DefaultParameterConverter} 816 case "nullint64": 817 // TODO(coopernurse): add type-specific converter 818 return driver.Null{Converter: driver.DefaultParameterConverter} 819 case "float64": 820 // TODO(coopernurse): add type-specific converter 821 return driver.NotNull{Converter: driver.DefaultParameterConverter} 822 case "nullfloat64": 823 // TODO(coopernurse): add type-specific converter 824 return driver.Null{Converter: driver.DefaultParameterConverter} 825 case "datetime": 826 return driver.DefaultParameterConverter 827 } 828 panic("invalid fakedb column type of " + typ) 829 }