mirror of
https://github.com/golang/go.git
synced 2025-05-19 06:14:40 +00:00
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 <rsc@golang.org> Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
parent
26397f1383
commit
867bef93b9
@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error
|
var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error
|
||||||
@ -127,6 +128,18 @@ func convertAssign(dest, src interface{}) error {
|
|||||||
*d = s
|
*d = s
|
||||||
return nil
|
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:
|
case nil:
|
||||||
switch d := dest.(type) {
|
switch d := dest.(type) {
|
||||||
case *interface{}:
|
case *interface{}:
|
||||||
@ -226,7 +239,8 @@ func convertAssign(dest, src interface{}) error {
|
|||||||
s := asString(src)
|
s := asString(src)
|
||||||
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
|
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
|
||||||
if err != nil {
|
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)
|
dv.SetInt(i64)
|
||||||
return nil
|
return nil
|
||||||
@ -234,7 +248,8 @@ func convertAssign(dest, src interface{}) error {
|
|||||||
s := asString(src)
|
s := asString(src)
|
||||||
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
|
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
|
||||||
if err != nil {
|
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)
|
dv.SetUint(u64)
|
||||||
return nil
|
return nil
|
||||||
@ -242,13 +257,21 @@ func convertAssign(dest, src interface{}) error {
|
|||||||
s := asString(src)
|
s := asString(src)
|
||||||
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
|
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
|
||||||
if err != nil {
|
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)
|
dv.SetFloat(f64)
|
||||||
return nil
|
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 {
|
func cloneBytes(b []byte) []byte {
|
||||||
|
@ -77,6 +77,14 @@ var conversionTests = []conversionTest{
|
|||||||
{s: uint64(123), d: &scanstr, wantstr: "123"},
|
{s: uint64(123), d: &scanstr, wantstr: "123"},
|
||||||
{s: 1.5, d: &scanstr, wantstr: "1.5"},
|
{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
|
// To []byte
|
||||||
{s: nil, d: &scanbytes, wantbytes: nil},
|
{s: nil, d: &scanbytes, wantbytes: nil},
|
||||||
{s: "string", d: &scanbytes, wantbytes: []byte("string")},
|
{s: "string", d: &scanbytes, wantbytes: []byte("string")},
|
||||||
@ -104,10 +112,16 @@ var conversionTests = []conversionTest{
|
|||||||
|
|
||||||
// Strings to integers
|
// Strings to integers
|
||||||
{s: "255", d: &scanuint8, wantuint: 255},
|
{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: "256", d: &scanuint16, wantuint: 256},
|
||||||
{s: "-1", d: &scanint, wantint: -1},
|
{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
|
// True bools
|
||||||
{s: true, d: &scanbool, wantbool: true},
|
{s: true, d: &scanbool, wantbool: true},
|
||||||
@ -155,7 +169,10 @@ var conversionTests = []conversionTest{
|
|||||||
{s: 1.5, d: new(userDefined), wantusrdef: 1.5},
|
{s: 1.5, d: new(userDefined), wantusrdef: 1.5},
|
||||||
{s: int64(123), d: new(userDefined), wantusrdef: 123},
|
{s: int64(123), d: new(userDefined), wantusrdef: 123},
|
||||||
{s: "1.5", d: new(userDefined), wantusrdef: 1.5},
|
{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{} {
|
func intPtrValue(intptr interface{}) interface{} {
|
||||||
|
@ -189,8 +189,7 @@ func (n NullBool) Value() (driver.Value, error) {
|
|||||||
type Scanner interface {
|
type Scanner interface {
|
||||||
// Scan assigns a value from a database driver.
|
// Scan assigns a value from a database driver.
|
||||||
//
|
//
|
||||||
// The src value will be of one of the following restricted
|
// The src value will be of one of the following types:
|
||||||
// set of types:
|
|
||||||
//
|
//
|
||||||
// int64
|
// int64
|
||||||
// float64
|
// float64
|
||||||
@ -1786,17 +1785,56 @@ func (rs *Rows) Columns() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan copies the columns in the current row into the values pointed
|
// 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
|
// Scan converts columns read from the database into the following
|
||||||
// of the corresponding data. The copy is owned by the caller and can
|
// common Go types and special types provided by the sql package:
|
||||||
// be modified and held indefinitely. The copy can be avoided by using
|
//
|
||||||
// an argument of type *RawBytes instead; see the documentation for
|
// *string
|
||||||
// RawBytes for restrictions on its use.
|
// *[]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
|
// If an argument has type *interface{}, Scan copies the value
|
||||||
// provided by the underlying driver without conversion. If the value
|
// provided by the underlying driver without conversion. When scanning
|
||||||
// is of type []byte, a copy is made and the caller owns the result.
|
// 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 {
|
func (rs *Rows) Scan(dest ...interface{}) error {
|
||||||
if rs.closed {
|
if rs.closed {
|
||||||
return errors.New("sql: Rows are 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
|
// Scan copies the columns from the matched row into the values
|
||||||
// pointed at by dest. If more than one row matches the query,
|
// pointed at by dest. See the documentation on Rows.Scan for details.
|
||||||
// Scan uses the first row and discards the rest. If no row matches
|
// 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.
|
// the query, Scan returns ErrNoRows.
|
||||||
func (r *Row) Scan(dest ...interface{}) error {
|
func (r *Row) Scan(dest ...interface{}) error {
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user