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