exp/sql{,/driver}: new database packages

R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
diff --git a/src/pkg/exp/sql/fakedb_test.go b/src/pkg/exp/sql/fakedb_test.go
new file mode 100644
index 0000000..40f60f7
--- /dev/null
+++ b/src/pkg/exp/sql/fakedb_test.go
@@ -0,0 +1,497 @@
+// 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 (
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+
+	"exp/sql/driver"
+)
+
+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
+// syntantically 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 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
+	openCount int
+	dbs       map[string]*fakeDB
+}
+
+type fakeDB struct {
+	name string
+
+	mu     sync.Mutex
+	free   []*fakeConn
+	tables map[string]*table
+}
+
+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
+}
+
+type fakeTx struct {
+	c *fakeConn
+}
+
+type fakeStmt struct {
+	c *fakeConn
+	q string // just for debugging
+
+	cmd   string
+	table string
+
+	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)
+}
+
+// Supports dsn forms:
+//    <dbname>
+//    <dbname>;wipe
+func (d *fakeDriver) Open(dsn string) (driver.Conn, os.Error) {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+	d.openCount++
+	if d.dbs == nil {
+		d.dbs = make(map[string]*fakeDB)
+	}
+	parts := strings.Split(dsn, ";")
+	if len(parts) < 1 {
+		return nil, os.NewError("fakedb: no database name")
+	}
+	name := parts[0]
+	db, ok := d.dbs[name]
+	if !ok {
+		db = &fakeDB{name: name}
+		d.dbs[name] = db
+	}
+	return &fakeConn{db: db}, nil
+}
+
+func (db *fakeDB) wipe() {
+	db.mu.Lock()
+	defer db.mu.Unlock()
+	db.tables = nil
+}
+
+func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) os.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",
+			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) Begin() (driver.Tx, os.Error) {
+	if c.currTx != nil {
+		return nil, os.NewError("already in a transaction")
+	}
+	c.currTx = &fakeTx{c: c}
+	return c.currTx, nil
+}
+
+func (c *fakeConn) Close() os.Error {
+	if c.currTx != nil {
+		return os.NewError("can't close; in a Transaction")
+	}
+	if c.db == nil {
+		return os.NewError("can't close; already closed")
+	}
+	c.db = nil
+	return nil
+}
+
+func errf(msg string, args ...interface{}) os.Error {
+	return os.NewError("fakedb: " + fmt.Sprintf(msg, args...))
+}
+
+// parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=?
+// (note that where where columns must always contain ? marks,
+//  just a limitation for fakedb)
+func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (driver.Stmt, os.Error) {
+	if len(parts) != 3 {
+		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], ",") {
+		nameVal := strings.Split(colspec, "=")
+		if len(nameVal) != 2 {
+			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 {
+			return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column)
+		}
+		if value != "?" {
+			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, os.Error) {
+	if len(parts) != 2 {
+		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 {
+			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, os.Error) {
+	if len(parts) != 2 {
+		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 {
+			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 {
+			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 "int32":
+				i, err := strconv.Atoi(value)
+				if err != nil {
+					return nil, errf("invalid conversion to int32 from %q", value)
+				}
+				subsetVal = int64(i) // int64 is a subset type, but not int32
+			default:
+				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
+}
+
+func (c *fakeConn) Prepare(query string) (driver.Stmt, os.Error) {
+	if c.db == nil {
+		panic("nil c.db; conn = " + fmt.Sprintf("%#v", c))
+	}
+	parts := strings.Split(query, "|")
+	if len(parts) < 1 {
+		return nil, errf("empty query")
+	}
+	cmd := parts[0]
+	parts = parts[1:]
+	stmt := &fakeStmt{q: query, c: c, cmd: cmd}
+	switch cmd {
+	case "WIPE":
+		// Nothing
+	case "SELECT":
+		return c.prepareSelect(stmt, parts)
+	case "CREATE":
+		return c.prepareCreate(stmt, parts)
+	case "INSERT":
+		return c.prepareInsert(stmt, parts)
+	default:
+		return nil, errf("unsupported command type %q", cmd)
+	}
+	return stmt, nil
+}
+
+func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter {
+	return s.placeholderConverter[idx]
+}
+
+func (s *fakeStmt) Close() os.Error {
+	return nil
+}
+
+func (s *fakeStmt) Exec(args []interface{}) (driver.Result, os.Error) {
+	db := s.c.db
+	switch s.cmd {
+	case "WIPE":
+		db.wipe()
+		return driver.DDLSuccess, nil
+	case "CREATE":
+		if err := db.createTable(s.table, s.colName, s.colType); err != nil {
+			return nil, err
+		}
+		return driver.DDLSuccess, nil
+	case "INSERT":
+		return s.execInsert(args)
+	}
+	fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s)
+	return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd)
+}
+
+func (s *fakeStmt) execInsert(args []interface{}) (driver.Result, os.Error) {
+	db := s.c.db
+	if len(args) != s.placeholders {
+		panic("error in pkg db; should only get here if size is correct")
+	}
+	db.mu.Lock()
+	t, ok := db.table(s.table)
+	db.mu.Unlock()
+	if !ok {
+		return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
+	}
+
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	cols := make([]interface{}, len(t.colname))
+	argPos := 0
+	for n, colname := range s.colName {
+		colidx := t.columnIndex(colname)
+		if colidx == -1 {
+			return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname)
+		}
+		var val interface{}
+		if strvalue, ok := s.colValue[n].(string); ok && strvalue == "?" {
+			val = args[argPos]
+			argPos++
+		} else {
+			val = s.colValue[n]
+		}
+		cols[colidx] = val
+	}
+
+	t.rows = append(t.rows, &row{cols: cols})
+	return driver.RowsAffected(1), nil
+}
+
+func (s *fakeStmt) Query(args []interface{}) (driver.Rows, os.Error) {
+	db := s.c.db
+	if len(args) != s.placeholders {
+		panic("error in pkg db; should only get here if size is correct")
+	}
+
+	db.mu.Lock()
+	t, ok := db.table(s.table)
+	db.mu.Unlock()
+	if !ok {
+		return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
+	}
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	colIdx := make(map[string]int) // select column name -> column index in table
+	for _, name := range s.colName {
+		idx := t.columnIndex(name)
+		if idx == -1 {
+			return nil, fmt.Errorf("fakedb: unknown column name %q", name)
+		}
+		colIdx[name] = idx
+	}
+
+	mrows := []*row{}
+rows:
+	for _, trow := range t.rows {
+		// Process the where clause, skipping non-match rows. This is lazy
+		// and just uses fmt.Sprintf("%v") to test equality.  Good enough
+		// for test code.
+		for widx, wcol := range s.whereCol {
+			idx := t.columnIndex(wcol)
+			if idx == -1 {
+				return nil, fmt.Errorf("db: invalid where clause column %q", wcol)
+			}
+			tcol := trow.cols[idx]
+			if bs, ok := tcol.([]byte); ok {
+				// lazy hack to avoid sprintf %v on a []byte
+				tcol = string(bs)
+			}
+			if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", args[widx]) {
+				continue rows
+			}
+		}
+		mrow := &row{cols: make([]interface{}, len(s.colName))}
+		for seli, name := range s.colName {
+			mrow.cols[seli] = trow.cols[colIdx[name]]
+		}
+		mrows = append(mrows, mrow)
+	}
+
+	cursor := &rowsCursor{
+		pos:  -1,
+		rows: mrows,
+		cols: s.colName,
+	}
+	return cursor, nil
+}
+
+func (s *fakeStmt) NumInput() int {
+	return s.placeholders
+}
+
+func (tx *fakeTx) Commit() os.Error {
+	tx.c.currTx = nil
+	return nil
+}
+
+func (tx *fakeTx) Rollback() os.Error {
+	tx.c.currTx = nil
+	return nil
+}
+
+type rowsCursor struct {
+	cols   []string
+	pos    int
+	rows   []*row
+	closed bool
+}
+
+func (rc *rowsCursor) Close() os.Error {
+	rc.closed = true
+	return nil
+}
+
+func (rc *rowsCursor) Columns() []string {
+	return rc.cols
+}
+
+func (rc *rowsCursor) Next(dest []interface{}) os.Error {
+	if rc.closed {
+		return os.NewError("fakedb: cursor is closed")
+	}
+	rc.pos++
+	if rc.pos >= len(rc.rows) {
+		return os.EOF // per interface spec
+	}
+	for i, v := range rc.rows[rc.pos].cols {
+		// TODO(bradfitz): convert to subset types? naah, I
+		// think the subset types should only be input to
+		// driver, but the db package should be able to handle
+		// a wider range of types coming out of drivers. all
+		// for ease of drivers, and to prevent drivers from
+		// messing up conversions or doing them differently.
+		dest[i] = v
+	}
+	return nil
+}
+
+func converterForType(typ string) driver.ValueConverter {
+	switch typ {
+	case "bool":
+		return driver.Bool
+	case "int32":
+		return driver.Int32
+	case "string":
+		return driver.String
+	}
+	panic("invalid fakedb column type of " + typ)
+}