From 867bef93b9a60ef07f068bc8d2d4398b76331e3c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 26 Jan 2016 20:58:00 +0000 Subject: [PATCH] database/sql: implement Scan of time.Time, document, clarify Scan error text Fixes #9157 Change-Id: Iadf305a172a0ec53ae91e1b2db3f3351691a48ff Reviewed-on: https://go-review.googlesource.com/18935 Reviewed-by: Russ Cox Reviewed-by: Andrew Gerrand --- src/database/sql/convert.go | 31 ++++++++++++++-- src/database/sql/convert_test.go | 23 ++++++++++-- src/database/sql/sql.go | 63 ++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go index bba5a8843a0fcf..740fd9d6e7c9af 100644 --- a/src/database/sql/convert.go +++ b/src/database/sql/convert.go @@ -12,6 +12,7 @@ import ( "fmt" "reflect" "strconv" + "time" ) var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error @@ -127,6 +128,18 @@ func convertAssign(dest, src interface{}) error { *d = s return nil } + case time.Time: + switch d := dest.(type) { + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + } case nil: switch d := dest.(type) { case *interface{}: @@ -226,7 +239,8 @@ func convertAssign(dest, src interface{}) error { s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetInt(i64) return nil @@ -234,7 +248,8 @@ func convertAssign(dest, src interface{}) error { s := asString(src) u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetUint(u64) return nil @@ -242,13 +257,21 @@ func convertAssign(dest, src interface{}) error { s := asString(src) f64, err := strconv.ParseFloat(s, dv.Type().Bits()) if err != nil { - return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err) + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetFloat(f64) return nil } - return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) +} + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err } func cloneBytes(b []byte) []byte { diff --git a/src/database/sql/convert_test.go b/src/database/sql/convert_test.go index 1fab282b40896a..342875e190c99e 100644 --- a/src/database/sql/convert_test.go +++ b/src/database/sql/convert_test.go @@ -77,6 +77,14 @@ var conversionTests = []conversionTest{ {s: uint64(123), d: &scanstr, wantstr: "123"}, {s: 1.5, d: &scanstr, wantstr: "1.5"}, + // From time.Time: + {s: time.Unix(1, 0).UTC(), d: &scanstr, wantstr: "1970-01-01T00:00:01Z"}, + {s: time.Unix(1453874597, 0).In(time.FixedZone("here", -3600*8)), d: &scanstr, wantstr: "2016-01-26T22:03:17-08:00"}, + {s: time.Unix(1, 2).UTC(), d: &scanstr, wantstr: "1970-01-01T00:00:01.000000002Z"}, + {s: time.Time{}, d: &scanstr, wantstr: "0001-01-01T00:00:00Z"}, + {s: time.Unix(1, 2).UTC(), d: &scanbytes, wantbytes: []byte("1970-01-01T00:00:01.000000002Z")}, + {s: time.Unix(1, 2).UTC(), d: &scaniface, wantiface: time.Unix(1, 2).UTC()}, + // To []byte {s: nil, d: &scanbytes, wantbytes: nil}, {s: "string", d: &scanbytes, wantbytes: []byte("string")}, @@ -104,10 +112,16 @@ var conversionTests = []conversionTest{ // Strings to integers {s: "255", d: &scanuint8, wantuint: 255}, - {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: strconv.ParseUint: parsing "256": value out of range`}, + {s: "256", d: &scanuint8, wanterr: "converting driver.Value type string (\"256\") to a uint8: value out of range"}, {s: "256", d: &scanuint16, wantuint: 256}, {s: "-1", d: &scanint, wantint: -1}, - {s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: strconv.ParseInt: parsing "foo": invalid syntax`}, + {s: "foo", d: &scanint, wanterr: "converting driver.Value type string (\"foo\") to a int: invalid syntax"}, + + // int64 to smaller integers + {s: int64(5), d: &scanuint8, wantuint: 5}, + {s: int64(256), d: &scanuint8, wanterr: "converting driver.Value type int64 (\"256\") to a uint8: value out of range"}, + {s: int64(256), d: &scanuint16, wantuint: 256}, + {s: int64(65536), d: &scanuint16, wanterr: "converting driver.Value type int64 (\"65536\") to a uint16: value out of range"}, // True bools {s: true, d: &scanbool, wantbool: true}, @@ -155,7 +169,10 @@ var conversionTests = []conversionTest{ {s: 1.5, d: new(userDefined), wantusrdef: 1.5}, {s: int64(123), d: new(userDefined), wantusrdef: 123}, {s: "1.5", d: new(userDefined), wantusrdef: 1.5}, - {s: []byte{1, 2, 3}, d: new(userDefinedSlice), wanterr: `unsupported driver -> Scan pair: []uint8 -> *sql.userDefinedSlice`}, + {s: []byte{1, 2, 3}, d: new(userDefinedSlice), wanterr: `unsupported Scan, storing driver.Value type []uint8 into type *sql.userDefinedSlice`}, + + // Other errors + {s: complex(1, 2), d: &scanstr, wanterr: `unsupported Scan, storing driver.Value type complex128 into type *string`}, } func intPtrValue(intptr interface{}) interface{} { diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go index b5a17f0fc1ac35..d8e7cb77af396e 100644 --- a/src/database/sql/sql.go +++ b/src/database/sql/sql.go @@ -189,8 +189,7 @@ func (n NullBool) Value() (driver.Value, error) { type Scanner interface { // Scan assigns a value from a database driver. // - // The src value will be of one of the following restricted - // set of types: + // The src value will be of one of the following types: // // int64 // float64 @@ -1786,17 +1785,56 @@ func (rs *Rows) Columns() ([]string, error) { } // Scan copies the columns in the current row into the values pointed -// at by dest. +// at by dest. The number of values in dest must be the same as the +// number of columns in Rows. // -// If an argument has type *[]byte, Scan saves in that argument a copy -// of the corresponding data. The copy is owned by the caller and can -// be modified and held indefinitely. The copy can be avoided by using -// an argument of type *RawBytes instead; see the documentation for -// RawBytes for restrictions on its use. +// Scan converts columns read from the database into the following +// common Go types and special types provided by the sql package: +// +// *string +// *[]byte +// *int, *int8, *int16, *int32, *int64 +// *uint, *uint8, *uint16, *uint32, *uint64 +// *bool +// *float32, *float64 +// *interface{} +// *RawBytes +// any type implementing Scanner (see Scanner docs) +// +// In the most simple case, if the type of the value from the source +// column is an integer, bool or string type T and dest is of type *T, +// Scan simply assigns the value through the pointer. +// +// Scan also converts between string and numeric types, as long as no +// information would be lost. While Scan stringifies all numbers +// scanned from numeric database columns into *string, scans into +// numeric types are checked for overflow. For example, a float64 with +// value 300 or a string with value "300" can scan into a uint16, but +// not into a uint8, though float64(255) or "255" can scan into a +// uint8. One exception is that scans of some float64 numbers to +// strings may lose information when stringifying. In general, scan +// floating point columns into *float64. +// +// If a dest argument has type *[]byte, Scan saves in that argument a +// copy of the corresponding data. The copy is owned by the caller and +// can be modified and held indefinitely. The copy can be avoided by +// using an argument of type *RawBytes instead; see the documentation +// for RawBytes for restrictions on its use. // // If an argument has type *interface{}, Scan copies the value -// provided by the underlying driver without conversion. If the value -// is of type []byte, a copy is made and the caller owns the result. +// provided by the underlying driver without conversion. When scanning +// from a source value of type []byte to *interface{}, a copy of the +// slice is made and the caller owns the result. +// +// Source values of type time.Time may be scanned into values of type +// *time.Time, *interface{}, *string, or *[]byte. When converting to +// the latter two, time.Format3339Nano is used. +// +// Source values of type bool may be scanned into types *bool, +// *interface{}, *string, *[]byte, or *RawBytes. +// +// For scanning into *bool, the source may be true, false, 1, 0, or +// string inputs parseable by strconv.ParseBool. func (rs *Rows) Scan(dest ...interface{}) error { if rs.closed { return errors.New("sql: Rows are closed") @@ -1845,8 +1883,9 @@ type Row struct { } // Scan copies the columns from the matched row into the values -// pointed at by dest. If more than one row matches the query, -// Scan uses the first row and discards the rest. If no row matches +// pointed at by dest. See the documentation on Rows.Scan for details. +// If more than one row matches the query, +// Scan uses the first row and discards the rest. If no row matches // the query, Scan returns ErrNoRows. func (r *Row) Scan(dest ...interface{}) error { if r.err != nil {