camli/db: start of type conversion work

Change-Id: I5467eee286e1752b3961c6fe3018fb78795b362f
This commit is contained in:
Brad Fitzpatrick 2011-08-25 17:31:24 +04:00
parent a9853a4622
commit 3c886de92b
4 changed files with 86 additions and 11 deletions

View File

@ -226,8 +226,26 @@ func (s *Stmt) Exec(args ...interface{}) os.Error {
return err
}
defer s.db.putConn(ci)
// TODO(bradfitz): convert args from full set (package db) to
// restricted set (package dbimpl)
if want := si.NumInput(); len(args) != want {
return fmt.Errorf("db: expected %d arguments, got %d", want, len(args))
}
// Convert args if the driver knows its own types.
if cc, ok := si.(dbimpl.ColumnConverter); ok {
for n, arg := range args {
args[n], err = cc.ColumnCoverter(n).ConvertValue(arg)
if err != nil {
return fmt.Errorf("db: converting Exec column index %d: %v", n, err)
}
}
}
// Then convert everything into the restricted subset
// of types that the dbimpl package needs to know about.
// all integers -> int64, etc
// TODO(bradfitz): ^that
resi, err := si.Exec(args)
if err != nil {
return err

View File

@ -49,12 +49,12 @@ func TestDb(t *testing.T) {
{[]interface{}{7, 9}, ""},
// Invalid conversions:
//{[]interface{}{"Brad", int64(0xFFFFFFFF)}, "conversion"},
//{[]interface{}{"Brad", "strconv fail"}, "conversion"},
{[]interface{}{"Brad", int64(0xFFFFFFFF)}, "db: converting Exec column index 1: value 4294967295 overflows int32"},
{[]interface{}{"Brad", "strconv fail"}, "db: converting Exec column index 1: value \"strconv fail\" can't be converted to int32"},
// Wrong number of args:
{[]interface{}{}, "fakedb: expected 2 arguments, got 0"},
{[]interface{}{1, 2, 3}, "fakedb: expected 2 arguments, got 3"},
{[]interface{}{}, "db: expected 2 arguments, got 0"},
{[]interface{}{1, 2, 3}, "db: expected 2 arguments, got 3"},
}
for n, et := range execTests {
err := stmt.Exec(et.args...)

View File

@ -66,6 +66,16 @@ type Stmt interface {
Query(args []interface{}) (Rows, os.Error)
}
// ColumnConverter may be optionally implemented by Stmt to signal
// to the db package to do type conversions.
type ColumnConverter interface {
ColumnCoverter(idx int) ValueConverter
}
type ValueConverter interface {
ConvertValue(v interface{}) (interface{}, os.Error)
}
type Rows interface {
Columns() []string
Close() os.Error

View File

@ -62,6 +62,15 @@ type table struct {
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
}
@ -87,6 +96,8 @@ type fakeStmt struct {
colType []string // used by CREATE
colValue []string // used by INSERT (mix of strings and "?" for bound params)
placeholders int // number of ? params
placeholderConverter []dbimpl.ValueConverter // used by INSERT
}
var driver dbimpl.Driver = &fakeDriver{}
@ -236,9 +247,9 @@ func (c *fakeConn) Prepare(query string) (dbimpl.Stmt, os.Error) {
// TODO(bradfitz): check that
// pre-bound value type conversion is
// valid for this column type
_ = ctype
} else {
stmt.placeholders++
stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype))
}
stmt.colName = append(stmt.colName, column)
stmt.colValue = append(stmt.colValue, value)
@ -249,6 +260,10 @@ func (c *fakeConn) Prepare(query string) (dbimpl.Stmt, os.Error) {
return stmt, nil
}
func (s *fakeStmt) ColumnCoverter(idx int) dbimpl.ValueConverter {
return s.placeholderConverter[idx]
}
func (s *fakeStmt) Close() os.Error {
return nil
}
@ -274,7 +289,7 @@ func (s *fakeStmt) Exec(args []interface{}) (dbimpl.Result, os.Error) {
func (s *fakeStmt) execInsert(args []interface{}) (dbimpl.Result, os.Error) {
db := s.c.db
if len(args) != s.placeholders {
return nil, fmt.Errorf("fakedb: expected %d arguments, got %d", s.placeholders, len(args))
panic("error in pkg db; should only get here if size is correct")
}
db.mu.Lock()
t, ok := db.table(s.table)
@ -286,9 +301,29 @@ func (s *fakeStmt) execInsert(args []interface{}) (dbimpl.Result, os.Error) {
t.mu.Lock()
defer t.mu.Unlock()
// TODO(bradfitz): type check columns, fill in defaults, etc
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 s.colValue[n] == "?" {
val = args[argPos]
argPos++
} else {
val = s.colValue[n]
}
valType := fmt.Sprintf("%T", val)
if colType := t.coltype[colidx]; valType != colType {
return nil, fmt.Errorf("fakedb: column %q value of %v (%v) doesn't match column type of %q",
colname, val, valType, colType)
}
cols[colidx] = val
}
//t.rows = append(t.rows, &row{cols: vals})
t.rows = append(t.rows, &row{cols: cols})
return dbimpl.RowsAffected(1), nil
}
@ -299,7 +334,7 @@ func (s *fakeStmt) Query(args []interface{}) (dbimpl.Rows, os.Error) {
}
func (s *fakeStmt) NumInput() int {
return 0
return s.placeholders
}
func (tx *fakeTx) Commit() os.Error {
@ -311,3 +346,15 @@ func (tx *fakeTx) Rollback() os.Error {
tx.c.currTx = nil
return nil
}
func converterForType(typ string) dbimpl.ValueConverter {
switch typ {
case "bool":
return dbimpl.Bool
case "int32":
return dbimpl.Int32
case "string":
return dbimpl.String
}
panic("invalid fakedb column type of " + typ)
}