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