From f38e41de89aa5079f80bfb9bbaf4dca95941dcd9 Mon Sep 17 00:00:00 2001 From: Arne Hormann Date: Thu, 12 Dec 2013 18:51:03 +0100 Subject: [PATCH 1/3] changed binary date / datetime to string --- packets.go | 4 +-- utils.go | 100 +++++++++++++++++++++++++++++------------------------ 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/packets.go b/packets.go index 68f4378d8..21513b27b 100644 --- a/packets.go +++ b/packets.go @@ -1048,7 +1048,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error { if rows.mc.parseTime { dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc) } else { - dest[i], err = formatBinaryDate(num, data[pos:]) + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], false) } if err == nil { @@ -1116,7 +1116,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error { if rows.mc.parseTime { dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc) } else { - dest[i], err = formatBinaryDateTime(num, data[pos:]) + dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], true) } if err == nil { diff --git a/utils.go b/utils.go index 9324806b2..5485e0c4f 100644 --- a/utils.go +++ b/utils.go @@ -503,55 +503,63 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num) } -func formatBinaryDate(num uint64, data []byte) (driver.Value, error) { - switch num { - case 0: - return []byte("0000-00-00"), nil +func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) { + const zeroDateTimeMicros = "0000-00-00 00:00:00.000000" + srclen := len(src) + var dst []byte + if withTime { + if srclen == 11 { + dst = []byte(zeroDateTimeMicros) + } else { + dst = []byte(zeroDateTimeMicros[:19]) + } + } else { + dst = []byte(zeroDateTimeMicros[:10]) + } + switch srclen { + case 11: + microsecs := binary.LittleEndian.Uint32(src[7:11]) + dst[20] += byte((microsecs / 100000) % 10) + dst[21] += byte((microsecs / 10000) % 10) + dst[22] += byte((microsecs / 1000) % 10) + dst[23] += byte((microsecs / 100) % 10) + dst[24] += byte((microsecs / 10) % 10) + dst[25] += byte(microsecs % 10) + fallthrough + case 7: + hour := src[4] + minute := src[5] + second := src[6] + dst[11] += (hour / 10) % 10 + dst[12] += hour % 10 + dst[14] += (minute / 10) % 10 + dst[15] += minute % 10 + dst[17] += (second / 10) % 10 + dst[18] += second % 10 + fallthrough case 4: - return []byte(fmt.Sprintf( - "%04d-%02d-%02d", - binary.LittleEndian.Uint16(data[:2]), - data[2], - data[3], - )), nil - } - return nil, fmt.Errorf("Invalid DATE-packet length %d", num) -} - -func formatBinaryDateTime(num uint64, data []byte) (driver.Value, error) { - switch num { + year := binary.LittleEndian.Uint16(src[:2]) + month := src[2] + day := src[3] + dst[0] += byte((year / 1000) % 10) + dst[1] += byte((year / 100) % 10) + dst[2] += byte((year / 10) % 10) + dst[3] += byte(year % 10) + dst[5] += (month / 10) % 10 + dst[6] += month % 10 + dst[8] += (day / 10) % 10 + dst[9] += day % 10 + return dst, nil case 0: - return []byte("0000-00-00 00:00:00"), nil - case 4: - return []byte(fmt.Sprintf( - "%04d-%02d-%02d 00:00:00", - binary.LittleEndian.Uint16(data[:2]), - data[2], - data[3], - )), nil - case 7: - return []byte(fmt.Sprintf( - "%04d-%02d-%02d %02d:%02d:%02d", - binary.LittleEndian.Uint16(data[:2]), - data[2], - data[3], - data[4], - data[5], - data[6], - )), nil - case 11: - return []byte(fmt.Sprintf( - "%04d-%02d-%02d %02d:%02d:%02d.%06d", - binary.LittleEndian.Uint16(data[:2]), - data[2], - data[3], - data[4], - data[5], - data[6], - binary.LittleEndian.Uint32(data[7:11]), - )), nil + return dst, nil } - return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num) + var mode string + if withTime { + mode = "DATETIME" + } else { + mode = "DATE" + } + return nil, fmt.Errorf("invalid %s-packet length %d", mode, srclen) } /****************************************************************************** From 8f8d1a605b93afa1a7d5ddf561e38a4ef9ec5039 Mon Sep 17 00:00:00 2001 From: Arne Hormann Date: Fri, 13 Dec 2013 16:36:11 +0100 Subject: [PATCH 2/3] no longer cache len(...) --- utils.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 5485e0c4f..46eb73891 100644 --- a/utils.go +++ b/utils.go @@ -505,10 +505,9 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) { const zeroDateTimeMicros = "0000-00-00 00:00:00.000000" - srclen := len(src) var dst []byte if withTime { - if srclen == 11 { + if len(src) == 11 { dst = []byte(zeroDateTimeMicros) } else { dst = []byte(zeroDateTimeMicros[:19]) @@ -516,7 +515,7 @@ func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) { } else { dst = []byte(zeroDateTimeMicros[:10]) } - switch srclen { + switch len(src) { case 11: microsecs := binary.LittleEndian.Uint32(src[7:11]) dst[20] += byte((microsecs / 100000) % 10) @@ -559,7 +558,7 @@ func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) { } else { mode = "DATE" } - return nil, fmt.Errorf("invalid %s-packet length %d", mode, srclen) + return nil, fmt.Errorf("invalid %s-packet length %d", mode, len(src)) } /****************************************************************************** From ba3e6d404342964674d31950b9781516060f0467 Mon Sep 17 00:00:00 2001 From: Arne Hormann Date: Sun, 15 Dec 2013 10:39:41 +0100 Subject: [PATCH 3/3] applied modulo and allocation optimizations, added test --- utils.go | 90 ++++++++++++++++++++++++++++++++------------------- utils_test.go | 30 +++++++++++++++++ 2 files changed, 86 insertions(+), 34 deletions(-) diff --git a/utils.go b/utils.go index 46eb73891..b5100e465 100644 --- a/utils.go +++ b/utils.go @@ -503,62 +503,84 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num) } +// zeroDateTime is used in formatBinaryDateTime to avoid an allocation +// if the DATE or DATETIME has the zero value. +// It must never be changed. +// The current behavior depends on database/sql copying the result. +var zeroDateTime = []byte("0000-00-00 00:00:00") + func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) { - const zeroDateTimeMicros = "0000-00-00 00:00:00.000000" + if len(src) == 0 { + if withTime { + return zeroDateTime, nil + } + return zeroDateTime[:10], nil + } var dst []byte if withTime { if len(src) == 11 { - dst = []byte(zeroDateTimeMicros) + dst = []byte("0000-00-00 00:00:00.000000") } else { - dst = []byte(zeroDateTimeMicros[:19]) + dst = []byte("0000-00-00 00:00:00") } } else { - dst = []byte(zeroDateTimeMicros[:10]) + dst = []byte("0000-00-00") } switch len(src) { case 11: microsecs := binary.LittleEndian.Uint32(src[7:11]) - dst[20] += byte((microsecs / 100000) % 10) - dst[21] += byte((microsecs / 10000) % 10) - dst[22] += byte((microsecs / 1000) % 10) - dst[23] += byte((microsecs / 100) % 10) - dst[24] += byte((microsecs / 10) % 10) - dst[25] += byte(microsecs % 10) + tmp32 := microsecs / 10 + dst[25] += byte(microsecs - 10*tmp32) + tmp32, microsecs = tmp32/10, tmp32 + dst[24] += byte(microsecs - 10*tmp32) + tmp32, microsecs = tmp32/10, tmp32 + dst[23] += byte(microsecs - 10*tmp32) + tmp32, microsecs = tmp32/10, tmp32 + dst[22] += byte(microsecs - 10*tmp32) + tmp32, microsecs = tmp32/10, tmp32 + dst[21] += byte(microsecs - 10*tmp32) + dst[20] += byte(microsecs / 10) fallthrough case 7: - hour := src[4] - minute := src[5] second := src[6] - dst[11] += (hour / 10) % 10 - dst[12] += hour % 10 - dst[14] += (minute / 10) % 10 - dst[15] += minute % 10 - dst[17] += (second / 10) % 10 - dst[18] += second % 10 + tmp := second / 10 + dst[18] += second - 10*tmp + dst[17] += tmp + minute := src[5] + tmp = minute / 10 + dst[15] += minute - 10*tmp + dst[14] += tmp + hour := src[4] + tmp = hour / 10 + dst[12] += hour - 10*tmp + dst[11] += tmp fallthrough case 4: - year := binary.LittleEndian.Uint16(src[:2]) - month := src[2] day := src[3] - dst[0] += byte((year / 1000) % 10) - dst[1] += byte((year / 100) % 10) - dst[2] += byte((year / 10) % 10) - dst[3] += byte(year % 10) - dst[5] += (month / 10) % 10 - dst[6] += month % 10 - dst[8] += (day / 10) % 10 - dst[9] += day % 10 - return dst, nil - case 0: + tmp := day / 10 + dst[9] += day - 10*tmp + dst[8] += tmp + month := src[2] + tmp = month / 10 + dst[6] += month - 10*tmp + dst[5] += tmp + year := binary.LittleEndian.Uint16(src[:2]) + tmp16 := year / 10 + dst[3] += byte(year - 10*tmp16) + tmp16, year = tmp16/10, tmp16 + dst[2] += byte(year - 10*tmp16) + tmp16, year = tmp16/10, tmp16 + dst[1] += byte(year - 10*tmp16) + dst[0] += byte(tmp16) return dst, nil } - var mode string + var t string if withTime { - mode = "DATETIME" + t = "DATETIME" } else { - mode = "DATE" + t = "DATE" } - return nil, fmt.Errorf("invalid %s-packet length %d", mode, len(src)) + return nil, fmt.Errorf("invalid %s-packet length %d", t, len(src)) } /****************************************************************************** diff --git a/utils_test.go b/utils_test.go index 233214d04..baf2b8c26 100644 --- a/utils_test.go +++ b/utils_test.go @@ -10,6 +10,7 @@ package mysql import ( "bytes" + "encoding/binary" "fmt" "testing" "time" @@ -180,3 +181,32 @@ func TestOldPass(t *testing.T) { } } } + +func TestFormatBinaryDateTime(t *testing.T) { + rawDate := [11]byte{} + binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years + rawDate[2] = 12 // months + rawDate[3] = 30 // days + rawDate[4] = 15 // hours + rawDate[5] = 46 // minutes + rawDate[6] = 23 // seconds + binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds + expect := func(expected string, length int, withTime bool) { + actual, _ := formatBinaryDateTime(rawDate[:length], withTime) + bytes, ok := actual.([]byte) + if !ok { + t.Errorf("formatBinaryDateTime must return []byte, was %T", actual) + } + if string(bytes) != expected { + t.Errorf( + "expected %q, got %q for length %d, withTime %v", + bytes, actual, length, withTime, + ) + } + } + expect("0000-00-00", 0, false) + expect("0000-00-00 00:00:00", 0, true) + expect("1978-12-30", 4, false) + expect("1978-12-30 15:46:23", 7, true) + expect("1978-12-30 15:46:23.987654", 11, true) +}