blob: a993fd46ede488938419a84ea6c112b6e321a6cb [file] [log] [blame]
Colin Cross7bb052a2015-02-03 12:59:37 -08001// 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
5package sql
6
7import (
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
21var _ = 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.
38type 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
47type fakeDB struct {
48 name string
49
50 mu sync.Mutex
51 free []*fakeConn
52 tables map[string]*table
53 badConn bool
54}
55
56type table struct {
57 mu sync.Mutex
58 colname []string
59 coltype []string
60 rows []*row
61}
62
63func (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
72type row struct {
73 cols []interface{} // must be same size as its table colname + coltype
74}
75
76func (r *row) clone() *row {
77 nrow := &row{cols: make([]interface{}, len(r.cols))}
78 copy(nrow.cols, r.cols)
79 return nrow
80}
81
82type 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
95func (c *fakeConn) incrStat(v *int) {
96 c.mu.Lock()
97 *v++
98 c.mu.Unlock()
99}
100
101type fakeTx struct {
102 c *fakeConn
103}
104
105type 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
124var fdriver driver.Driver = &fakeDriver{}
125
126func init() {
127 Register("test", fdriver)
128}
129
130func 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
139type Dummy struct {
140 driver.Driver
141}
142
143func 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())
158func (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
184func (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
198func (db *fakeDB) wipe() {
199 db.mu.Lock()
200 defer db.mu.Unlock()
201 db.tables = nil
202}
203
204func (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
222func (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
230func (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
245func (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
255func (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
266var hookPostCloseConn struct {
267 sync.Mutex
268 fn func(*fakeConn, error)
269}
270
271func setHookpostCloseConn(fn func(*fakeConn, error)) {
272 hookPostCloseConn.Lock()
273 defer hookPostCloseConn.Unlock()
274 hookPostCloseConn.fn = fn
275}
276
277var 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.
281func setStrictFakeConnClose(t *testing.T) {
282 testStrictClose = t
283}
284
285func (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
316func 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
327func (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
339func (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
351func 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)
358func (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
392func (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
411func (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
461var hookPrepareBadConn func() bool
462
463func (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
501func (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
508func (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
522var errClosed = errors.New("fakedb: statement has been closed")
523
524// hook to simulate broken connections
525var hookExecBadConn func() bool
526
527func (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.
565func (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
609var hookQueryBadConn func() bool
610
611func (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{}
658rows:
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
693func (s *fakeStmt) NumInput() int {
694 return s.placeholders
695}
696
697func (tx *fakeTx) Commit() error {
698 tx.c.currTx = nil
699 return nil
700}
701
702func (tx *fakeTx) Rollback() error {
703 tx.c.currTx = nil
704 return nil
705}
706
707type 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
723func (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
733func (rc *rowsCursor) Columns() []string {
734 return rc.cols
735}
736
737var rowsCursorNextHook func(dest []driver.Value) error
738
739func (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//
786type fakeDriverString struct{}
787
788func (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
801func 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}