Commit 8089e578 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

exp/sql: finish transactions, flesh out types, docs

Fixes #2328 (float, bool)

R=rsc, r
CC=golang-dev
https://golang.org/cl/5294067
parent cefee3c9
......@@ -203,7 +203,6 @@ NOTEST+=\
exp/ebnflint\
exp/gui\
exp/gui/x11\
exp/sql/driver\
go/doc\
hash\
http/pprof\
......
......@@ -8,6 +8,7 @@ package sql
import (
"errors"
"exp/sql/driver"
"fmt"
"reflect"
"strconv"
......@@ -36,10 +37,11 @@ func convertAssign(dest, src interface{}) error {
}
}
sv := reflect.ValueOf(src)
var sv reflect.Value
switch d := dest.(type) {
case *string:
sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
......@@ -48,6 +50,12 @@ func convertAssign(dest, src interface{}) error {
*d = fmt.Sprintf("%v", src)
return nil
}
case *bool:
bv, err := driver.Bool.ConvertValue(src)
if err == nil {
*d = bv.(bool)
}
return err
}
if scanner, ok := dest.(ScannerInto); ok {
......@@ -59,6 +67,10 @@ func convertAssign(dest, src interface{}) error {
return errors.New("destination not a pointer")
}
if !sv.IsValid() {
sv = reflect.ValueOf(src)
}
dv := reflect.Indirect(dpv)
if dv.Kind() == sv.Kind() {
dv.Set(sv)
......@@ -67,40 +79,49 @@ func convertAssign(dest, src interface{}) error {
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if s, ok := asString(src); ok {
i64, err := strconv.Atoi64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowInt(i64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetInt(i64)
return nil
s := asString(src)
i64, err := strconv.Atoi64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowInt(i64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if s, ok := asString(src); ok {
u64, err := strconv.Atoui64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowUint(u64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetUint(u64)
return nil
s := asString(src)
u64, err := strconv.Atoui64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowUint(u64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
s := asString(src)
f64, err := strconv.Atof64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowFloat(f64) {
return fmt.Errorf("value %q overflows %s", s, dv.Kind())
}
dv.SetFloat(f64)
return nil
}
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)
}
func asString(src interface{}) (s string, ok bool) {
func asString(src interface{}) string {
switch v := src.(type) {
case string:
return v, true
return v
case []byte:
return string(v), true
return string(v)
}
return "", false
return fmt.Sprintf("%v", src)
}
......@@ -17,6 +17,9 @@ type conversionTest struct {
wantint int64
wantuint uint64
wantstr string
wantf32 float32
wantf64 float64
wantbool bool // used if d is of type *bool
wanterr string
}
......@@ -29,6 +32,9 @@ var (
scanint32 int32
scanuint8 uint8
scanuint16 uint16
scanbool bool
scanf32 float32
scanf64 float64
)
var conversionTests = []conversionTest{
......@@ -53,6 +59,35 @@ var conversionTests = []conversionTest{
{s: "256", d: &scanuint16, wantuint: 256},
{s: "-1", d: &scanint, wantint: -1},
{s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: parsing "foo": invalid syntax`},
// True bools
{s: true, d: &scanbool, wantbool: true},
{s: "True", d: &scanbool, wantbool: true},
{s: "TRUE", d: &scanbool, wantbool: true},
{s: "1", d: &scanbool, wantbool: true},
{s: 1, d: &scanbool, wantbool: true},
{s: int64(1), d: &scanbool, wantbool: true},
{s: uint16(1), d: &scanbool, wantbool: true},
// False bools
{s: false, d: &scanbool, wantbool: false},
{s: "false", d: &scanbool, wantbool: false},
{s: "FALSE", d: &scanbool, wantbool: false},
{s: "0", d: &scanbool, wantbool: false},
{s: 0, d: &scanbool, wantbool: false},
{s: int64(0), d: &scanbool, wantbool: false},
{s: uint16(0), d: &scanbool, wantbool: false},
// Not bools
{s: "yup", d: &scanbool, wanterr: `sql/driver: couldn't convert "yup" into type bool`},
{s: 2, d: &scanbool, wanterr: `sql/driver: couldn't convert 2 into type bool`},
// Floats
{s: float64(1.5), d: &scanf64, wantf64: float64(1.5)},
{s: int64(1), d: &scanf64, wantf64: float64(1)},
{s: float64(1.5), d: &scanf32, wantf32: float32(1.5)},
{s: "1.5", d: &scanf32, wantf32: float32(1.5)},
{s: "1.5", d: &scanf64, wantf64: float64(1.5)},
}
func intValue(intptr interface{}) int64 {
......@@ -63,6 +98,14 @@ func uintValue(intptr interface{}) uint64 {
return reflect.Indirect(reflect.ValueOf(intptr)).Uint()
}
func float64Value(ptr interface{}) float64 {
return *(ptr.(*float64))
}
func float32Value(ptr interface{}) float32 {
return *(ptr.(*float32))
}
func TestConversions(t *testing.T) {
for n, ct := range conversionTests {
err := convertAssign(ct.d, ct.s)
......@@ -86,6 +129,15 @@ func TestConversions(t *testing.T) {
if ct.wantuint != 0 && ct.wantuint != uintValue(ct.d) {
errf("want uint %d, got %d", ct.wantuint, uintValue(ct.d))
}
if ct.wantf32 != 0 && ct.wantf32 != float32Value(ct.d) {
errf("want float32 %v, got %v", ct.wantf32, float32Value(ct.d))
}
if ct.wantf64 != 0 && ct.wantf64 != float64Value(ct.d) {
errf("want float32 %v, got %v", ct.wantf64, float64Value(ct.d))
}
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
errf("want bool %v, got %v", ct.wantbool, *bp)
}
}
}
......
......@@ -24,9 +24,13 @@ import "errors"
// Driver is the interface that must be implemented by a database
// driver.
type Driver interface {
// Open returns a new or cached connection to the database.
// Open returns a new connection to the database.
// The name is a string in a driver-specific format.
//
// Open may return a cached connection (one previously
// closed), but doing so is unnecessary; the sql package
// maintains a pool of idle connections for efficient re-use.
//
// The returned connection is only used by one goroutine at a
// time.
Open(name string) (Conn, error)
......@@ -59,8 +63,12 @@ type Conn interface {
// Close invalidates and potentially stops any current
// prepared statements and transactions, marking this
// connection as no longer in use. The driver may cache or
// close its underlying connection to its database.
// connection as no longer in use.
//
// Because the sql package maintains a free pool of
// connections and only calls Close when there's a surplus of
// idle connections, it shouldn't be necessary for drivers to
// do their own connection caching.
Close() error
// Begin starts and returns a new transaction.
......
......@@ -11,6 +11,21 @@ import (
)
// ValueConverter is the interface providing the ConvertValue method.
//
// Various implementations of ValueConverter are provided by the
// driver package to provide consistent implementations of conversions
// between drivers. The ValueConverters have several uses:
//
// * converting from the subset types as provided by the sql package
// into a database table's specific column type and making sure it
// fits, such as making sure a particular int64 fits in a
// table's uint16 column.
//
// * converting a value as given from the database into one of the
// subset types.
//
// * by the sql package, for converting from a driver's subset type
// to a user's type in a scan.
type ValueConverter interface {
// ConvertValue converts a value to a restricted subset type.
ConvertValue(v interface{}) (interface{}, error)
......@@ -19,15 +34,56 @@ type ValueConverter interface {
// Bool is a ValueConverter that converts input values to bools.
//
// The conversion rules are:
// - .... TODO(bradfitz): TBD
// - booleans are returned unchanged
// - for integer types,
// 1 is true
// 0 is false,
// other integers are an error
// - for strings and []byte, same rules as strconv.Atob
// - all other types are an error
var Bool boolType
type boolType struct{}
var _ ValueConverter = boolType{}
func (boolType) ConvertValue(v interface{}) (interface{}, error) {
return nil, fmt.Errorf("TODO(bradfitz): bool conversions")
func (boolType) String() string { return "Bool" }
func (boolType) ConvertValue(src interface{}) (interface{}, error) {
switch s := src.(type) {
case bool:
return s, nil
case string:
b, err := strconv.Atob(s)
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
case []byte:
b, err := strconv.Atob(string(s))
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
}
sv := reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
iv := sv.Int()
if iv == 1 || iv == 0 {
return iv == 1, nil
}
return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
uv := sv.Uint()
if uv == 1 || uv == 0 {
return uv == 1, nil
}
return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv)
}
return nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src)
}
// Int32 is a ValueConverter that converts input values to int64,
......
// 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 driver
import (
"reflect"
"testing"
)
type valueConverterTest struct {
c ValueConverter
in interface{}
out interface{}
err string
}
var valueConverterTests = []valueConverterTest{
{Bool, "true", true, ""},
{Bool, "True", true, ""},
{Bool, []byte("t"), true, ""},
{Bool, true, true, ""},
{Bool, "1", true, ""},
{Bool, 1, true, ""},
{Bool, int64(1), true, ""},
{Bool, uint16(1), true, ""},
{Bool, "false", false, ""},
{Bool, false, false, ""},
{Bool, "0", false, ""},
{Bool, 0, false, ""},
{Bool, int64(0), false, ""},
{Bool, uint16(0), false, ""},
{c: Bool, in: "foo", err: "sql/driver: couldn't convert \"foo\" into type bool"},
{c: Bool, in: 2, err: "sql/driver: couldn't convert 2 into type bool"},
}
func TestValueConverters(t *testing.T) {
for i, tt := range valueConverterTests {
out, err := tt.c.ConvertValue(tt.in)
goterr := ""
if err != nil {
goterr = err.Error()
}
if goterr != tt.err {
t.Errorf("test %d: %s(%T(%v)) error = %q; want error = %q",
i, tt.c, tt.in, tt.in, goterr, tt.err)
}
if tt.err != "" {
continue
}
if !reflect.DeepEqual(out, tt.out) {
t.Errorf("test %d: %s(%T(%v)) = %v (%T); want %v (%T)",
i, tt.c, tt.in, tt.in, out, out, tt.out, tt.out)
}
}
}
......@@ -476,7 +476,7 @@ func (rc *rowsCursor) Next(dest []interface{}) error {
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
// driver, but the sql 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.
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment