Skip to content

Commit d9aa6d3

Browse files
dolmenmethane
authored andcommitted
Refactor NullTime as go1.13's sql.NullTime (go-sql-driver#995)
1 parent 23821f4 commit d9aa6d3

6 files changed

+177
-95
lines changed

nulltime.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
package mysql
10+
11+
import (
12+
"database/sql/driver"
13+
"fmt"
14+
"time"
15+
)
16+
17+
// Scan implements the Scanner interface.
18+
// The value type must be time.Time or string / []byte (formatted time-string),
19+
// otherwise Scan fails.
20+
func (nt *NullTime) Scan(value interface{}) (err error) {
21+
if value == nil {
22+
nt.Time, nt.Valid = time.Time{}, false
23+
return
24+
}
25+
26+
switch v := value.(type) {
27+
case time.Time:
28+
nt.Time, nt.Valid = v, true
29+
return
30+
case []byte:
31+
nt.Time, err = parseDateTime(string(v), time.UTC)
32+
nt.Valid = (err == nil)
33+
return
34+
case string:
35+
nt.Time, err = parseDateTime(v, time.UTC)
36+
nt.Valid = (err == nil)
37+
return
38+
}
39+
40+
nt.Valid = false
41+
return fmt.Errorf("Can't convert %T to time.Time", value)
42+
}
43+
44+
// Value implements the driver Valuer interface.
45+
func (nt NullTime) Value() (driver.Value, error) {
46+
if !nt.Valid {
47+
return nil, nil
48+
}
49+
return nt.Time, nil
50+
}

nulltime_go113.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
// +build go1.13
10+
11+
package mysql
12+
13+
import (
14+
"database/sql"
15+
)
16+
17+
// NullTime represents a time.Time that may be NULL.
18+
// NullTime implements the Scanner interface so
19+
// it can be used as a scan destination:
20+
//
21+
// var nt NullTime
22+
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
23+
// ...
24+
// if nt.Valid {
25+
// // use nt.Time
26+
// } else {
27+
// // NULL value
28+
// }
29+
//
30+
// This NullTime implementation is not driver-specific
31+
type NullTime sql.NullTime

nulltime_legacy.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
// +build !go1.13
10+
11+
package mysql
12+
13+
import (
14+
"time"
15+
)
16+
17+
// NullTime represents a time.Time that may be NULL.
18+
// NullTime implements the Scanner interface so
19+
// it can be used as a scan destination:
20+
//
21+
// var nt NullTime
22+
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
23+
// ...
24+
// if nt.Valid {
25+
// // use nt.Time
26+
// } else {
27+
// // NULL value
28+
// }
29+
//
30+
// This NullTime implementation is not driver-specific
31+
type NullTime struct {
32+
Time time.Time
33+
Valid bool // Valid is true if Time is not NULL
34+
}

nulltime_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
package mysql
10+
11+
import (
12+
"database/sql"
13+
"database/sql/driver"
14+
"testing"
15+
"time"
16+
)
17+
18+
var (
19+
// Check implementation of interfaces
20+
_ driver.Valuer = NullTime{}
21+
_ sql.Scanner = (*NullTime)(nil)
22+
)
23+
24+
func TestScanNullTime(t *testing.T) {
25+
var scanTests = []struct {
26+
in interface{}
27+
error bool
28+
valid bool
29+
time time.Time
30+
}{
31+
{tDate, false, true, tDate},
32+
{sDate, false, true, tDate},
33+
{[]byte(sDate), false, true, tDate},
34+
{tDateTime, false, true, tDateTime},
35+
{sDateTime, false, true, tDateTime},
36+
{[]byte(sDateTime), false, true, tDateTime},
37+
{tDate0, false, true, tDate0},
38+
{sDate0, false, true, tDate0},
39+
{[]byte(sDate0), false, true, tDate0},
40+
{sDateTime0, false, true, tDate0},
41+
{[]byte(sDateTime0), false, true, tDate0},
42+
{"", true, false, tDate0},
43+
{"1234", true, false, tDate0},
44+
{0, true, false, tDate0},
45+
}
46+
47+
var nt = NullTime{}
48+
var err error
49+
50+
for _, tst := range scanTests {
51+
err = nt.Scan(tst.in)
52+
if (err != nil) != tst.error {
53+
t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
54+
}
55+
if nt.Valid != tst.valid {
56+
t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
57+
}
58+
if nt.Time != tst.time {
59+
t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
60+
}
61+
}
62+
}

utils.go

-54
Original file line numberDiff line numberDiff line change
@@ -106,60 +106,6 @@ func readBool(input string) (value bool, valid bool) {
106106
* Time related utils *
107107
******************************************************************************/
108108

109-
// NullTime represents a time.Time that may be NULL.
110-
// NullTime implements the Scanner interface so
111-
// it can be used as a scan destination:
112-
//
113-
// var nt NullTime
114-
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
115-
// ...
116-
// if nt.Valid {
117-
// // use nt.Time
118-
// } else {
119-
// // NULL value
120-
// }
121-
//
122-
// This NullTime implementation is not driver-specific
123-
type NullTime struct {
124-
Time time.Time
125-
Valid bool // Valid is true if Time is not NULL
126-
}
127-
128-
// Scan implements the Scanner interface.
129-
// The value type must be time.Time or string / []byte (formatted time-string),
130-
// otherwise Scan fails.
131-
func (nt *NullTime) Scan(value interface{}) (err error) {
132-
if value == nil {
133-
nt.Time, nt.Valid = time.Time{}, false
134-
return
135-
}
136-
137-
switch v := value.(type) {
138-
case time.Time:
139-
nt.Time, nt.Valid = v, true
140-
return
141-
case []byte:
142-
nt.Time, err = parseDateTime(string(v), time.UTC)
143-
nt.Valid = (err == nil)
144-
return
145-
case string:
146-
nt.Time, err = parseDateTime(v, time.UTC)
147-
nt.Valid = (err == nil)
148-
return
149-
}
150-
151-
nt.Valid = false
152-
return fmt.Errorf("Can't convert %T to time.Time", value)
153-
}
154-
155-
// Value implements the driver Valuer interface.
156-
func (nt NullTime) Value() (driver.Value, error) {
157-
if !nt.Valid {
158-
return nil, nil
159-
}
160-
return nt.Time, nil
161-
}
162-
163109
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
164110
base := "0000-00-00 00:00:00.0000000"
165111
switch len(str) {

utils_test.go

-41
Original file line numberDiff line numberDiff line change
@@ -14,49 +14,8 @@ import (
1414
"database/sql/driver"
1515
"encoding/binary"
1616
"testing"
17-
"time"
1817
)
1918

20-
func TestScanNullTime(t *testing.T) {
21-
var scanTests = []struct {
22-
in interface{}
23-
error bool
24-
valid bool
25-
time time.Time
26-
}{
27-
{tDate, false, true, tDate},
28-
{sDate, false, true, tDate},
29-
{[]byte(sDate), false, true, tDate},
30-
{tDateTime, false, true, tDateTime},
31-
{sDateTime, false, true, tDateTime},
32-
{[]byte(sDateTime), false, true, tDateTime},
33-
{tDate0, false, true, tDate0},
34-
{sDate0, false, true, tDate0},
35-
{[]byte(sDate0), false, true, tDate0},
36-
{sDateTime0, false, true, tDate0},
37-
{[]byte(sDateTime0), false, true, tDate0},
38-
{"", true, false, tDate0},
39-
{"1234", true, false, tDate0},
40-
{0, true, false, tDate0},
41-
}
42-
43-
var nt = NullTime{}
44-
var err error
45-
46-
for _, tst := range scanTests {
47-
err = nt.Scan(tst.in)
48-
if (err != nil) != tst.error {
49-
t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
50-
}
51-
if nt.Valid != tst.valid {
52-
t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
53-
}
54-
if nt.Time != tst.time {
55-
t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
56-
}
57-
}
58-
}
59-
6019
func TestLengthEncodedInteger(t *testing.T) {
6120
var integerTests = []struct {
6221
num uint64

0 commit comments

Comments
 (0)