From ad990db8fb811b278ab28865ac989376941ba249 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 11:44:02 +0900 Subject: [PATCH 01/11] fixed the way of parsing datetime when byte slice string The benchmark results $ go test -benchmem . -bench "^BenchmarkParseByte" goos: darwin goarch: amd64 pkg: github.com/go-sql-driver/mysql BenchmarkParseByteDateTime-4 12023173 104 ns/op 0 B/op 0 allocs/op BenchmarkParseByteDateTimeStringCast-4 3394239 355 ns/op 32 B/op 1 allocs/op --- nulltime.go | 2 +- packets.go | 4 +- utils.go | 141 +++++++++++++++++++++++++++++++++++++++++++++++++- utils_test.go | 88 +++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 5 deletions(-) diff --git a/nulltime.go b/nulltime.go index afa8a89e9..45515f751 100644 --- a/nulltime.go +++ b/nulltime.go @@ -28,7 +28,7 @@ func (nt *NullTime) Scan(value interface{}) (err error) { nt.Time, nt.Valid = v, true return case []byte: - nt.Time, err = parseDateTime(string(v), time.UTC) + nt.Time, err = parseByteDateTime(v, time.UTC) nt.Valid = (err == nil) return case string: diff --git a/packets.go b/packets.go index 5cbd53298..176702a63 100644 --- a/packets.go +++ b/packets.go @@ -777,8 +777,8 @@ func (rows *textRows) readRow(dest []driver.Value) error { switch rows.rs.columns[i].fieldType { case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeDate, fieldTypeNewDate: - dest[i], err = parseDateTime( - string(dest[i].([]byte)), + dest[i], err = parseByteDateTime( + dest[i].([]byte), mc.cfg.Loc, ) if err == nil { diff --git a/utils.go b/utils.go index 154ecc337..33f85d9ac 100644 --- a/utils.go +++ b/utils.go @@ -9,6 +9,7 @@ package mysql import ( + "bytes" "crypto/tls" "database/sql" "database/sql/driver" @@ -106,11 +107,15 @@ func readBool(input string) (value bool, valid bool) { * Time related utils * ******************************************************************************/ +var ( + nullTimeBaseStr = "0000-00-00 00:00:00.0000000" + nullTimeBaseByte = []byte(nullTimeBaseStr) +) + func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { - base := "0000-00-00 00:00:00.0000000" switch len(str) { case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" - if str == base[:len(str)] { + if str == nullTimeBaseStr[:len(str)] { return } if loc == time.UTC { @@ -123,6 +128,138 @@ func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { } } +func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { + switch len(b) { + case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" + if bytes.Compare(b, nullTimeBaseByte[:len(b)]) == 0 { + return time.Time{}, nil + } + + year, err := parseByteYear(b) + if err != nil { + return time.Time{}, err + } + if year <= 0 { + year = 1 + } + + if b[4] != '-' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4]) + } + + m, err := parseByte2Digits(b, 5) + if err != nil { + return time.Time{}, err + } + if m <= 0 { + m = 1 + } + month := time.Month(m) + + if b[7] != '-' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7]) + } + + day, err := parseByte2Digits(b, 8) + if err != nil { + return time.Time{}, err + } + if day <= 0 { + day = 1 + } + if len(b) == 10 { + return time.Date(year, month, day, 0, 0, 0, 0, loc), nil + } + + if b[10] != ' ' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[10]) + } + + hour, err := parseByte2Digits(b, 11) + if err != nil { + return time.Time{}, err + } + if b[13] != ':' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[13]) + } + + min, err := parseByte2Digits(b, 14) + if err != nil { + return time.Time{}, err + } + if b[16] != ':' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[16]) + } + + sec, err := parseByte2Digits(b, 17) + if err != nil { + return time.Time{}, err + } + if len(b) == 19 { + return time.Date(year, month, day, hour, min, sec, 0, loc), nil + } + + if b[19] != '.' { + return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19]) + } + nsec, err := parseByteNanoSec(b) + if err != nil { + return time.Time{}, err + } + return time.Date(year, month, day, hour, min, sec, nsec, loc), nil + default: + return time.Time{}, fmt.Errorf("invalid time bytes: %s", b) + } +} + +func parseByteYear(b []byte) (int, error) { + year, n := 0, 1000 + for i := 0; i < 4; i++ { + v, err := bToi(b[i]) + if err != nil { + return 0, fmt.Errorf("invalid time bytes: %s", b) + } + year += v * n + n = n / 10 + } + return year, nil +} + +func parseByte2Digits(b []byte, index int) (int, error) { + d2, err := bToi(b[index]) + if err != nil { + return 0, fmt.Errorf("invalid time bytes: %s", b) + } + d1, err := bToi(b[index+1]) + if err != nil { + return 0, fmt.Errorf("invalid time bytes: %s", b) + } + return d2*10 + d1, nil +} + +func parseByteNanoSec(b []byte) (int, error) { + l := len(b) + ns, digit := 0, 100000 // max is 6-digits + for i := 20; i < l; i++ { + v, err := bToi(b[i]) + if err != nil { + return 0, err + } + ns += v * digit + digit /= 10 + } + // nanoseconds has 10-digits. + // 10 - 6 = 4, so we have to multiple 1000. + return ns * 1000, nil +} + +func bToi(b byte) (int, error) { + if b < '0' || b > '9' { + return 0, errors.New("not [0-9]") + } + return int(b - '0'), nil +} + func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) { switch num { case 0: diff --git a/utils_test.go b/utils_test.go index ab29cad78..9a9d2a216 100644 --- a/utils_test.go +++ b/utils_test.go @@ -327,6 +327,76 @@ func TestParseDateTime(t *testing.T) { } } +func TestParseByteDateTime(t *testing.T) { + cases := []struct { + name string + str string + }{ + { + name: "parse date", + str: "2020-05-13", + }, + { + name: "parse null date", + str: sDate0, + }, + { + name: "parse datetime", + str: "2020-05-13 21:30:45", + }, + { + name: "parse null datetime", + str: sDateTime0, + }, + { + name: "parse datetime nanosec 1-digit", + str: "2020-05-25 23:22:01.1", + }, + { + name: "parse datetime nanosec 2-digits", + str: "2020-05-25 23:22:01.15", + }, + { + name: "parse datetime nanosec 3-digits", + str: "2020-05-25 23:22:01.159", + }, + { + name: "parse datetime nanosec 4-digits", + str: "2020-05-25 23:22:01.1594", + }, + { + name: "parse datetime nanosec 5-digits", + str: "2020-05-25 23:22:01.15949", + }, + { + name: "parse datetime nanosec 6-digits", + str: "2020-05-25 23:22:01.159491", + }, + } + + for _, loc := range []*time.Location{ + time.UTC, + time.FixedZone("test", 8*60*60), + } { + for _, cc := range cases { + t.Run(cc.name+"-"+loc.String(), func(t *testing.T) { + want, err := parseDateTime(cc.str, loc) + if err != nil { + t.Fatal(err) + } + got, err := parseByteDateTime([]byte(cc.str), loc) + if err != nil { + t.Fatal(err) + } + + if !want.Equal(got) { + t.Fatalf("want: %v, but got %v", want, got) + } + }) + } + } +} + func BenchmarkParseDateTime(b *testing.B) { str := "2020-05-13 21:30:45" loc := time.FixedZone("test", 8*60*60) @@ -334,3 +404,21 @@ func BenchmarkParseDateTime(b *testing.B) { _, _ = parseDateTime(str, loc) } } + +func BenchmarkParseByteDateTime(b *testing.B) { + bStr := []byte("2020-05-25 23:22:01.159491") + loc := time.FixedZone("test", 8*60*60) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = parseByteDateTime(bStr, loc) + } +} + +func BenchmarkParseByteDateTimeStringCast(b *testing.B) { + bStr := []byte("2020-05-25 23:22:01.159491") + loc := time.FixedZone("test", 8*60*60) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = parseDateTime(string(bStr), loc) + } +} From 5201e8abd8ae69994342380a642b5c91b2d65243 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 11:55:36 +0900 Subject: [PATCH 02/11] added line to AUTHORS file --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index e24231d81..fb6d003f6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -55,6 +55,7 @@ Julien Schmidt Justin Li Justin Nuß Kamil Dziedzic +Kei Kamikawa Kevin Malachowski Kieron Woodhouse Lennart Rudolph From 3a0b99ca9985c24d4176e71ba4b0ec4d22382bdf Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 12:35:56 +0900 Subject: [PATCH 03/11] fixed error handling --- utils.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/utils.go b/utils.go index 33f85d9ac..00e1de51c 100644 --- a/utils.go +++ b/utils.go @@ -147,7 +147,7 @@ func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4]) } - m, err := parseByte2Digits(b, 5) + m, err := parseByte2Digits(b[5], b[6]) if err != nil { return time.Time{}, err } @@ -160,7 +160,7 @@ func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7]) } - day, err := parseByte2Digits(b, 8) + day, err := parseByte2Digits(b[8], b[9]) if err != nil { return time.Time{}, err } @@ -175,7 +175,7 @@ func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[10]) } - hour, err := parseByte2Digits(b, 11) + hour, err := parseByte2Digits(b[11], b[12]) if err != nil { return time.Time{}, err } @@ -183,7 +183,7 @@ func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[13]) } - min, err := parseByte2Digits(b, 14) + min, err := parseByte2Digits(b[14], b[15]) if err != nil { return time.Time{}, err } @@ -191,7 +191,7 @@ func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[16]) } - sec, err := parseByte2Digits(b, 17) + sec, err := parseByte2Digits(b[17], b[18]) if err != nil { return time.Time{}, err } @@ -217,7 +217,7 @@ func parseByteYear(b []byte) (int, error) { for i := 0; i < 4; i++ { v, err := bToi(b[i]) if err != nil { - return 0, fmt.Errorf("invalid time bytes: %s", b) + return 0, err } year += v * n n = n / 10 @@ -225,14 +225,14 @@ func parseByteYear(b []byte) (int, error) { return year, nil } -func parseByte2Digits(b []byte, index int) (int, error) { - d2, err := bToi(b[index]) +func parseByte2Digits(b1, b2 byte) (int, error) { + d2, err := bToi(b1) if err != nil { - return 0, fmt.Errorf("invalid time bytes: %s", b) + return 0, err } - d1, err := bToi(b[index+1]) + d1, err := bToi(b2) if err != nil { - return 0, fmt.Errorf("invalid time bytes: %s", b) + return 0, err } return d2*10 + d1, nil } @@ -248,7 +248,7 @@ func parseByteNanoSec(b []byte) (int, error) { ns += v * digit digit /= 10 } - // nanoseconds has 10-digits. + // nanoseconds has 10-digits. (needs to scale digits) // 10 - 6 = 4, so we have to multiple 1000. return ns * 1000, nil } From 128451e302d40325e0be515d44df1ec20b06c278 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 12:49:53 +0900 Subject: [PATCH 04/11] fixed nanosec digits --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 00e1de51c..899956665 100644 --- a/utils.go +++ b/utils.go @@ -108,7 +108,7 @@ func readBool(input string) (value bool, valid bool) { ******************************************************************************/ var ( - nullTimeBaseStr = "0000-00-00 00:00:00.0000000" + nullTimeBaseStr = "0000-00-00 00:00:00.000000" nullTimeBaseByte = []byte(nullTimeBaseStr) ) From b7a549276d9fd7032f8f6c79e6f1a887031a9d9c Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 12:53:00 +0900 Subject: [PATCH 05/11] added more tests for error --- utils_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/utils_test.go b/utils_test.go index 9a9d2a216..c7bda4a5f 100644 --- a/utils_test.go +++ b/utils_test.go @@ -397,6 +397,75 @@ func TestParseByteDateTime(t *testing.T) { } } +func TestParseByteDateTimeFail(t *testing.T) { + cases := []struct { + name string + str string + wantErr string + }{ + { + name: "parse invalid time", + str: "hello", + wantErr: "invalid time bytes: hello", + }, + { + name: "parse year", + str: "000!-00-00 00:00:00.000000", + wantErr: "not [0-9]", + }, + { + name: "parse month", + str: "0000-!0-00 00:00:00.000000", + wantErr: "not [0-9]", + }, + { + name: `parse "-" after parsed year`, + str: "0000:00-00 00:00:00.000000", + wantErr: "bad value for field: `:`", + }, + { + name: `parse "-" after parsed month`, + str: "0000-00:00 00:00:00.000000", + wantErr: "bad value for field: `:`", + }, + { + name: `parse " " after parsed date`, + str: "0000-00-00+00:00:00.000000", + wantErr: "bad value for field: `+`", + }, + { + name: `parse ":" after parsed date`, + str: "0000-00-00 00-00:00.000000", + wantErr: "bad value for field: `-`", + }, + { + name: `parse ":" after parsed hour`, + str: "0000-00-00 00:00-00.000000", + wantErr: "bad value for field: `-`", + }, + { + name: `parse "." after parsed sec`, + str: "0000-00-00 00:00:00?000000", + wantErr: "bad value for field: `?`", + }, + } + + for _, cc := range cases { + t.Run(cc.name, func(t *testing.T) { + got, err := parseByteDateTime([]byte(cc.str), time.UTC) + if err == nil { + t.Fatal("want error") + } + if cc.wantErr != err.Error() { + t.Fatalf("want `%s`, but got `%s`", cc.wantErr, err) + } + if !got.IsZero() { + t.Fatal("want zero time") + } + }) + } +} + func BenchmarkParseDateTime(b *testing.B) { str := "2020-05-13 21:30:45" loc := time.FixedZone("test", 8*60*60) From 7ba45c525a5c7cf4b0ae821db81a7f5b5bba6ff1 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 15:18:36 +0900 Subject: [PATCH 06/11] renamed parseByteDateTime to parseDateTime --- nulltime.go | 4 ++-- packets.go | 2 +- utils.go | 18 +---------------- utils_test.go | 55 ++++++++++++++++++--------------------------------- 4 files changed, 23 insertions(+), 56 deletions(-) diff --git a/nulltime.go b/nulltime.go index 45515f751..651723a96 100644 --- a/nulltime.go +++ b/nulltime.go @@ -28,11 +28,11 @@ func (nt *NullTime) Scan(value interface{}) (err error) { nt.Time, nt.Valid = v, true return case []byte: - nt.Time, err = parseByteDateTime(v, time.UTC) + nt.Time, err = parseDateTime(v, time.UTC) nt.Valid = (err == nil) return case string: - nt.Time, err = parseDateTime(v, time.UTC) + nt.Time, err = parseDateTime([]byte(v), time.UTC) nt.Valid = (err == nil) return } diff --git a/packets.go b/packets.go index 176702a63..8e2f5e76f 100644 --- a/packets.go +++ b/packets.go @@ -777,7 +777,7 @@ func (rows *textRows) readRow(dest []driver.Value) error { switch rows.rs.columns[i].fieldType { case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeDate, fieldTypeNewDate: - dest[i], err = parseByteDateTime( + dest[i], err = parseDateTime( dest[i].([]byte), mc.cfg.Loc, ) diff --git a/utils.go b/utils.go index 899956665..4c1fedb7e 100644 --- a/utils.go +++ b/utils.go @@ -112,23 +112,7 @@ var ( nullTimeBaseByte = []byte(nullTimeBaseStr) ) -func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { - switch len(str) { - case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" - if str == nullTimeBaseStr[:len(str)] { - return - } - if loc == time.UTC { - return time.Parse(timeFormat[:len(str)], str) - } - return time.ParseInLocation(timeFormat[:len(str)], str, loc) - default: - err = fmt.Errorf("invalid time string: %s", str) - return - } -} - -func parseByteDateTime(b []byte, loc *time.Location) (time.Time, error) { +func parseDateTime(b []byte, loc *time.Location) (time.Time, error) { switch len(b) { case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" if bytes.Compare(b, nullTimeBaseByte[:len(b)]) == 0 { diff --git a/utils_test.go b/utils_test.go index c7bda4a5f..099d7b196 100644 --- a/utils_test.go +++ b/utils_test.go @@ -13,6 +13,7 @@ import ( "database/sql" "database/sql/driver" "encoding/binary" + "fmt" "testing" "time" ) @@ -293,41 +294,23 @@ func TestIsolationLevelMapping(t *testing.T) { } } -func TestParseDateTime(t *testing.T) { - // UTC loc - { - str := "2020-05-13 21:30:45" - t1, err := parseDateTime(str, time.UTC) - if err != nil { - t.Error(err) - return - } - t2 := time.Date(2020, 5, 13, - 21, 30, 45, 0, time.UTC) - if !t1.Equal(t2) { - t.Errorf("want equal, have: %v, want: %v", t1, t2) - return - } - } - // non-UTC loc - { - str := "2020-05-13 21:30:45" - loc := time.FixedZone("test", 8*60*60) - t1, err := parseDateTime(str, loc) - if err != nil { - t.Error(err) +func deprecatedParseDateTime(str string, loc *time.Location) (t time.Time, err error) { + switch len(str) { + case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" + if str == nullTimeBaseStr[:len(str)] { return } - t2 := time.Date(2020, 5, 13, - 21, 30, 45, 0, loc) - if !t1.Equal(t2) { - t.Errorf("want equal, have: %v, want: %v", t1, t2) - return + if loc == time.UTC { + return time.Parse(timeFormat[:len(str)], str) } + return time.ParseInLocation(timeFormat[:len(str)], str, loc) + default: + err = fmt.Errorf("invalid time string: %s", str) + return } } -func TestParseByteDateTime(t *testing.T) { +func TestParseDateTime(t *testing.T) { cases := []struct { name string str string @@ -380,11 +363,11 @@ func TestParseByteDateTime(t *testing.T) { } { for _, cc := range cases { t.Run(cc.name+"-"+loc.String(), func(t *testing.T) { - want, err := parseDateTime(cc.str, loc) + want, err := deprecatedParseDateTime(cc.str, loc) if err != nil { t.Fatal(err) } - got, err := parseByteDateTime([]byte(cc.str), loc) + got, err := parseDateTime([]byte(cc.str), loc) if err != nil { t.Fatal(err) } @@ -397,7 +380,7 @@ func TestParseByteDateTime(t *testing.T) { } } -func TestParseByteDateTimeFail(t *testing.T) { +func TestParseDateTimeFail(t *testing.T) { cases := []struct { name string str string @@ -452,7 +435,7 @@ func TestParseByteDateTimeFail(t *testing.T) { for _, cc := range cases { t.Run(cc.name, func(t *testing.T) { - got, err := parseByteDateTime([]byte(cc.str), time.UTC) + got, err := parseDateTime([]byte(cc.str), time.UTC) if err == nil { t.Fatal("want error") } @@ -470,7 +453,7 @@ func BenchmarkParseDateTime(b *testing.B) { str := "2020-05-13 21:30:45" loc := time.FixedZone("test", 8*60*60) for i := 0; i < b.N; i++ { - _, _ = parseDateTime(str, loc) + _, _ = deprecatedParseDateTime(str, loc) } } @@ -479,7 +462,7 @@ func BenchmarkParseByteDateTime(b *testing.B) { loc := time.FixedZone("test", 8*60*60) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = parseByteDateTime(bStr, loc) + _, _ = parseDateTime(bStr, loc) } } @@ -488,6 +471,6 @@ func BenchmarkParseByteDateTimeStringCast(b *testing.B) { loc := time.FixedZone("test", 8*60*60) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = parseDateTime(string(bStr), loc) + _, _ = deprecatedParseDateTime(string(bStr), loc) } } From c5a4dd2cb1b8d79de19d6d885adc6c23943830ca Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 26 May 2020 15:35:20 +0900 Subject: [PATCH 07/11] reverted base null time --- utils.go | 9 ++------- utils_test.go | 3 ++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/utils.go b/utils.go index 4c1fedb7e..e475cf36d 100644 --- a/utils.go +++ b/utils.go @@ -9,7 +9,6 @@ package mysql import ( - "bytes" "crypto/tls" "database/sql" "database/sql/driver" @@ -107,15 +106,11 @@ func readBool(input string) (value bool, valid bool) { * Time related utils * ******************************************************************************/ -var ( - nullTimeBaseStr = "0000-00-00 00:00:00.000000" - nullTimeBaseByte = []byte(nullTimeBaseStr) -) - func parseDateTime(b []byte, loc *time.Location) (time.Time, error) { + const base = "0000-00-00 00:00:00.000000" switch len(b) { case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" - if bytes.Compare(b, nullTimeBaseByte[:len(b)]) == 0 { + if string(b) == base[:len(b)] { return time.Time{}, nil } diff --git a/utils_test.go b/utils_test.go index 099d7b196..e56b9dcae 100644 --- a/utils_test.go +++ b/utils_test.go @@ -295,9 +295,10 @@ func TestIsolationLevelMapping(t *testing.T) { } func deprecatedParseDateTime(str string, loc *time.Location) (t time.Time, err error) { + const base = "0000-00-00 00:00:00.000000" switch len(str) { case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" - if str == nullTimeBaseStr[:len(str)] { + if str == base[:len(str)] { return } if loc == time.UTC { From 9e639e4e522d97095511aede020b0ec514a33792 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 28 May 2020 16:42:32 +0900 Subject: [PATCH 08/11] Update utils.go Co-authored-by: Inada Naoki --- utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils.go b/utils.go index e475cf36d..2c18871d5 100644 --- a/utils.go +++ b/utils.go @@ -205,15 +205,15 @@ func parseByteYear(b []byte) (int, error) { } func parseByte2Digits(b1, b2 byte) (int, error) { - d2, err := bToi(b1) + d1, err := bToi(b1) if err != nil { return 0, err } - d1, err := bToi(b2) + d2, err := bToi(b2) if err != nil { return 0, err } - return d2*10 + d1, nil + return d1*10 + d2, nil } func parseByteNanoSec(b []byte) (int, error) { From 0e3cd50735a9738d4cf6f814a3c91a4becfdc724 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 28 May 2020 16:42:42 +0900 Subject: [PATCH 09/11] Update utils.go Co-authored-by: Inada Naoki --- utils.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils.go b/utils.go index 2c18871d5..25a4994d1 100644 --- a/utils.go +++ b/utils.go @@ -217,9 +217,8 @@ func parseByte2Digits(b1, b2 byte) (int, error) { } func parseByteNanoSec(b []byte) (int, error) { - l := len(b) ns, digit := 0, 100000 // max is 6-digits - for i := 20; i < l; i++ { + for i := 0; i < len(b); i++ { v, err := bToi(b[i]) if err != nil { return 0, err From 32ad982a65288468dfaaf5e97532af70b5124a54 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 28 May 2020 16:43:21 +0900 Subject: [PATCH 10/11] Update utils.go Co-authored-by: Inada Naoki --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 25a4994d1..9dd3679c6 100644 --- a/utils.go +++ b/utils.go @@ -181,7 +181,7 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) { if b[19] != '.' { return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19]) } - nsec, err := parseByteNanoSec(b) + nsec, err := parseByteNanoSec(b[20:]) if err != nil { return time.Time{}, err } From 2c6aa27e88345456be824ebb72dd476cf69506c4 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 28 May 2020 17:00:41 +0900 Subject: [PATCH 11/11] removed deprecatedParseDateTime from test --- utils_test.go | 54 +++++++-------------------------------------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/utils_test.go b/utils_test.go index e56b9dcae..114f4b3da 100644 --- a/utils_test.go +++ b/utils_test.go @@ -13,7 +13,6 @@ import ( "database/sql" "database/sql/driver" "encoding/binary" - "fmt" "testing" "time" ) @@ -294,23 +293,6 @@ func TestIsolationLevelMapping(t *testing.T) { } } -func deprecatedParseDateTime(str string, loc *time.Location) (t time.Time, err error) { - const base = "0000-00-00 00:00:00.000000" - switch len(str) { - case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" - if str == base[:len(str)] { - return - } - if loc == time.UTC { - return time.Parse(timeFormat[:len(str)], str) - } - return time.ParseInLocation(timeFormat[:len(str)], str, loc) - default: - err = fmt.Errorf("invalid time string: %s", str) - return - } -} - func TestParseDateTime(t *testing.T) { cases := []struct { name string @@ -364,9 +346,13 @@ func TestParseDateTime(t *testing.T) { } { for _, cc := range cases { t.Run(cc.name+"-"+loc.String(), func(t *testing.T) { - want, err := deprecatedParseDateTime(cc.str, loc) - if err != nil { - t.Fatal(err) + var want time.Time + if cc.str != sDate0 && cc.str != sDateTime0 { + var err error + want, err = time.ParseInLocation(timeFormat[:len(cc.str)], cc.str, loc) + if err != nil { + t.Fatal(err) + } } got, err := parseDateTime([]byte(cc.str), loc) if err != nil { @@ -449,29 +435,3 @@ func TestParseDateTimeFail(t *testing.T) { }) } } - -func BenchmarkParseDateTime(b *testing.B) { - str := "2020-05-13 21:30:45" - loc := time.FixedZone("test", 8*60*60) - for i := 0; i < b.N; i++ { - _, _ = deprecatedParseDateTime(str, loc) - } -} - -func BenchmarkParseByteDateTime(b *testing.B) { - bStr := []byte("2020-05-25 23:22:01.159491") - loc := time.FixedZone("test", 8*60*60) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, _ = parseDateTime(bStr, loc) - } -} - -func BenchmarkParseByteDateTimeStringCast(b *testing.B) { - bStr := []byte("2020-05-25 23:22:01.159491") - loc := time.FixedZone("test", 8*60*60) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, _ = deprecatedParseDateTime(string(bStr), loc) - } -}