exp/sql: add time.Time support
Fixes #2694
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5541057
diff --git a/src/pkg/exp/sql/convert_test.go b/src/pkg/exp/sql/convert_test.go
index bed09ff..702ba43 100644
--- a/src/pkg/exp/sql/convert_test.go
+++ b/src/pkg/exp/sql/convert_test.go
@@ -8,8 +8,11 @@
"fmt"
"reflect"
"testing"
+ "time"
)
+var someTime = time.Unix(123, 0)
+
type conversionTest struct {
s, d interface{} // source and destination
@@ -19,6 +22,7 @@
wantstr string
wantf32 float32
wantf64 float64
+ wanttime time.Time
wantbool bool // used if d is of type *bool
wanterr string
}
@@ -35,12 +39,14 @@
scanbool bool
scanf32 float32
scanf64 float64
+ scantime time.Time
)
var conversionTests = []conversionTest{
// Exact conversions (destination pointer type matches source type)
{s: "foo", d: &scanstr, wantstr: "foo"},
{s: 123, d: &scanint, wantint: 123},
+ {s: someTime, d: &scantime, wanttime: someTime},
// To strings
{s: []byte("byteslice"), d: &scanstr, wantstr: "byteslice"},
@@ -106,6 +112,10 @@
return *(ptr.(*float32))
}
+func timeValue(ptr interface{}) time.Time {
+ return *(ptr.(*time.Time))
+}
+
func TestConversions(t *testing.T) {
for n, ct := range conversionTests {
err := convertAssign(ct.d, ct.s)
@@ -138,6 +148,9 @@
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
errf("want bool %v, got %v", ct.wantbool, *bp)
}
+ if !ct.wanttime.IsZero() && !ct.wanttime.Equal(timeValue(ct.d)) {
+ errf("want time %v, got %v", ct.wanttime, timeValue(ct.d))
+ }
}
}
diff --git a/src/pkg/exp/sql/driver/driver.go b/src/pkg/exp/sql/driver/driver.go
index f0bcca2..0cd2562 100644
--- a/src/pkg/exp/sql/driver/driver.go
+++ b/src/pkg/exp/sql/driver/driver.go
@@ -16,6 +16,7 @@
// nil
// []byte
// string [*] everywhere except from Rows.Next.
+// time.Time
//
package driver
diff --git a/src/pkg/exp/sql/driver/types.go b/src/pkg/exp/sql/driver/types.go
index 086b529..0ee2788 100644
--- a/src/pkg/exp/sql/driver/types.go
+++ b/src/pkg/exp/sql/driver/types.go
@@ -8,6 +8,7 @@
"fmt"
"reflect"
"strconv"
+ "time"
)
// ValueConverter is the interface providing the ConvertValue method.
@@ -143,9 +144,10 @@
// bool
// nil
// []byte
+// time.Time
// string
//
-// This is the ame list as IsScanSubsetType, with the addition of
+// This is the same list as IsScanSubsetType, with the addition of
// string.
func IsParameterSubsetType(v interface{}) bool {
if IsScanSubsetType(v) {
@@ -165,6 +167,7 @@
// bool
// nil
// []byte
+// time.Time
//
// This is the same list as IsParameterSubsetType, without string.
func IsScanSubsetType(v interface{}) bool {
@@ -172,7 +175,7 @@
return true
}
switch v.(type) {
- case int64, float64, []byte, bool:
+ case int64, float64, []byte, bool, time.Time:
return true
}
return false
diff --git a/src/pkg/exp/sql/driver/types_test.go b/src/pkg/exp/sql/driver/types_test.go
index 4b049e2..966bc6b 100644
--- a/src/pkg/exp/sql/driver/types_test.go
+++ b/src/pkg/exp/sql/driver/types_test.go
@@ -7,6 +7,7 @@
import (
"reflect"
"testing"
+ "time"
)
type valueConverterTest struct {
@@ -16,6 +17,8 @@
err string
}
+var now = time.Now()
+
var valueConverterTests = []valueConverterTest{
{Bool, "true", true, ""},
{Bool, "True", true, ""},
@@ -33,6 +36,7 @@
{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"},
+ {DefaultParameterConverter, now, now, ""},
}
func TestValueConverters(t *testing.T) {
diff --git a/src/pkg/exp/sql/fakedb_test.go b/src/pkg/exp/sql/fakedb_test.go
index d81c09e..70aa68c 100644
--- a/src/pkg/exp/sql/fakedb_test.go
+++ b/src/pkg/exp/sql/fakedb_test.go
@@ -12,6 +12,7 @@
"strconv"
"strings"
"sync"
+ "time"
"exp/sql/driver"
)
@@ -220,7 +221,7 @@
func checkSubsetTypes(args []interface{}) error {
for n, arg := range args {
switch arg.(type) {
- case int64, float64, bool, nil, []byte, string:
+ case int64, float64, bool, nil, []byte, string, time.Time:
default:
return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg)
}
@@ -589,6 +590,8 @@
return driver.Int32
case "string":
return driver.String
+ case "datetime":
+ return driver.DefaultParameterConverter
}
panic("invalid fakedb column type of " + typ)
}
diff --git a/src/pkg/exp/sql/sql_test.go b/src/pkg/exp/sql/sql_test.go
index 716d4ca..3f98a8c 100644
--- a/src/pkg/exp/sql/sql_test.go
+++ b/src/pkg/exp/sql/sql_test.go
@@ -8,10 +8,13 @@
"reflect"
"strings"
"testing"
+ "time"
)
const fakeDBName = "foo"
+var chrisBirthday = time.Unix(123456789, 0)
+
func newTestDB(t *testing.T, name string) *DB {
db, err := Open("test", fakeDBName)
if err != nil {
@@ -21,10 +24,10 @@
t.Fatalf("exec wipe: %v", err)
}
if name == "people" {
- exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool")
+ exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime")
exec(t, db, "INSERT|people|name=Alice,age=?,photo=APHOTO", 1)
exec(t, db, "INSERT|people|name=Bob,age=?,photo=BPHOTO", 2)
- exec(t, db, "INSERT|people|name=Chris,age=?,photo=CPHOTO", 3)
+ exec(t, db, "INSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday)
}
return db
}
@@ -105,12 +108,18 @@
defer closeDB(t, db)
var name string
var age int
+ var birthday time.Time
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
if err == nil || !strings.Contains(err.Error(), "expected 2 destination arguments") {
t.Errorf("expected error from wrong number of arguments; actually got: %v", err)
}
+ err = db.QueryRow("SELECT|people|bdate|age=?", 3).Scan(&birthday)
+ if err != nil || !birthday.Equal(chrisBirthday) {
+ t.Errorf("chris birthday = %v, err = %v; want %v", birthday, err, chrisBirthday)
+ }
+
err = db.QueryRow("SELECT|people|age,name|age=?", 2).Scan(&age, &name)
if err != nil {
t.Fatalf("age QueryRow+Scan: %v", err)