Skip to content

Commit 948093a

Browse files
committed
Merge pull request go-sql-driver#195 from arnehormann/faster-dateformat
changed binary date / datetime to string
2 parents e1da8ee + ba3e6d4 commit 948093a

File tree

3 files changed

+109
-50
lines changed

3 files changed

+109
-50
lines changed

packets.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
10561056
if rows.mc.parseTime {
10571057
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
10581058
} else {
1059-
dest[i], err = formatBinaryDate(num, data[pos:])
1059+
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], false)
10601060
}
10611061

10621062
if err == nil {
@@ -1124,7 +1124,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
11241124
if rows.mc.parseTime {
11251125
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
11261126
} else {
1127-
dest[i], err = formatBinaryDateTime(num, data[pos:])
1127+
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], true)
11281128
}
11291129

11301130
if err == nil {

utils.go

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -503,55 +503,84 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
503503
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
504504
}
505505

506-
func formatBinaryDate(num uint64, data []byte) (driver.Value, error) {
507-
switch num {
508-
case 0:
509-
return []byte("0000-00-00"), nil
510-
case 4:
511-
return []byte(fmt.Sprintf(
512-
"%04d-%02d-%02d",
513-
binary.LittleEndian.Uint16(data[:2]),
514-
data[2],
515-
data[3],
516-
)), nil
517-
}
518-
return nil, fmt.Errorf("Invalid DATE-packet length %d", num)
519-
}
520-
521-
func formatBinaryDateTime(num uint64, data []byte) (driver.Value, error) {
522-
switch num {
523-
case 0:
524-
return []byte("0000-00-00 00:00:00"), nil
525-
case 4:
526-
return []byte(fmt.Sprintf(
527-
"%04d-%02d-%02d 00:00:00",
528-
binary.LittleEndian.Uint16(data[:2]),
529-
data[2],
530-
data[3],
531-
)), nil
532-
case 7:
533-
return []byte(fmt.Sprintf(
534-
"%04d-%02d-%02d %02d:%02d:%02d",
535-
binary.LittleEndian.Uint16(data[:2]),
536-
data[2],
537-
data[3],
538-
data[4],
539-
data[5],
540-
data[6],
541-
)), nil
542-
case 11:
543-
return []byte(fmt.Sprintf(
544-
"%04d-%02d-%02d %02d:%02d:%02d.%06d",
545-
binary.LittleEndian.Uint16(data[:2]),
546-
data[2],
547-
data[3],
548-
data[4],
549-
data[5],
550-
data[6],
551-
binary.LittleEndian.Uint32(data[7:11]),
552-
)), nil
506+
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
507+
// if the DATE or DATETIME has the zero value.
508+
// It must never be changed.
509+
// The current behavior depends on database/sql copying the result.
510+
var zeroDateTime = []byte("0000-00-00 00:00:00")
511+
512+
func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) {
513+
if len(src) == 0 {
514+
if withTime {
515+
return zeroDateTime, nil
516+
}
517+
return zeroDateTime[:10], nil
518+
}
519+
var dst []byte
520+
if withTime {
521+
if len(src) == 11 {
522+
dst = []byte("0000-00-00 00:00:00.000000")
523+
} else {
524+
dst = []byte("0000-00-00 00:00:00")
525+
}
526+
} else {
527+
dst = []byte("0000-00-00")
553528
}
554-
return nil, fmt.Errorf("Invalid DATETIME-packet length %d", num)
529+
switch len(src) {
530+
case 11:
531+
microsecs := binary.LittleEndian.Uint32(src[7:11])
532+
tmp32 := microsecs / 10
533+
dst[25] += byte(microsecs - 10*tmp32)
534+
tmp32, microsecs = tmp32/10, tmp32
535+
dst[24] += byte(microsecs - 10*tmp32)
536+
tmp32, microsecs = tmp32/10, tmp32
537+
dst[23] += byte(microsecs - 10*tmp32)
538+
tmp32, microsecs = tmp32/10, tmp32
539+
dst[22] += byte(microsecs - 10*tmp32)
540+
tmp32, microsecs = tmp32/10, tmp32
541+
dst[21] += byte(microsecs - 10*tmp32)
542+
dst[20] += byte(microsecs / 10)
543+
fallthrough
544+
case 7:
545+
second := src[6]
546+
tmp := second / 10
547+
dst[18] += second - 10*tmp
548+
dst[17] += tmp
549+
minute := src[5]
550+
tmp = minute / 10
551+
dst[15] += minute - 10*tmp
552+
dst[14] += tmp
553+
hour := src[4]
554+
tmp = hour / 10
555+
dst[12] += hour - 10*tmp
556+
dst[11] += tmp
557+
fallthrough
558+
case 4:
559+
day := src[3]
560+
tmp := day / 10
561+
dst[9] += day - 10*tmp
562+
dst[8] += tmp
563+
month := src[2]
564+
tmp = month / 10
565+
dst[6] += month - 10*tmp
566+
dst[5] += tmp
567+
year := binary.LittleEndian.Uint16(src[:2])
568+
tmp16 := year / 10
569+
dst[3] += byte(year - 10*tmp16)
570+
tmp16, year = tmp16/10, tmp16
571+
dst[2] += byte(year - 10*tmp16)
572+
tmp16, year = tmp16/10, tmp16
573+
dst[1] += byte(year - 10*tmp16)
574+
dst[0] += byte(tmp16)
575+
return dst, nil
576+
}
577+
var t string
578+
if withTime {
579+
t = "DATETIME"
580+
} else {
581+
t = "DATE"
582+
}
583+
return nil, fmt.Errorf("invalid %s-packet length %d", t, len(src))
555584
}
556585

557586
/******************************************************************************

utils_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package mysql
1010

1111
import (
1212
"bytes"
13+
"encoding/binary"
1314
"fmt"
1415
"testing"
1516
"time"
@@ -180,3 +181,32 @@ func TestOldPass(t *testing.T) {
180181
}
181182
}
182183
}
184+
185+
func TestFormatBinaryDateTime(t *testing.T) {
186+
rawDate := [11]byte{}
187+
binary.LittleEndian.PutUint16(rawDate[:2], 1978) // years
188+
rawDate[2] = 12 // months
189+
rawDate[3] = 30 // days
190+
rawDate[4] = 15 // hours
191+
rawDate[5] = 46 // minutes
192+
rawDate[6] = 23 // seconds
193+
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
194+
expect := func(expected string, length int, withTime bool) {
195+
actual, _ := formatBinaryDateTime(rawDate[:length], withTime)
196+
bytes, ok := actual.([]byte)
197+
if !ok {
198+
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
199+
}
200+
if string(bytes) != expected {
201+
t.Errorf(
202+
"expected %q, got %q for length %d, withTime %v",
203+
bytes, actual, length, withTime,
204+
)
205+
}
206+
}
207+
expect("0000-00-00", 0, false)
208+
expect("0000-00-00 00:00:00", 0, true)
209+
expect("1978-12-30", 4, false)
210+
expect("1978-12-30 15:46:23", 7, true)
211+
expect("1978-12-30 15:46:23.987654", 11, true)
212+
}

0 commit comments

Comments
 (0)