Skip to content

Commit ba3e6d4

Browse files
committed
applied modulo and allocation optimizations, added test
1 parent 8f8d1a6 commit ba3e6d4

File tree

2 files changed

+86
-34
lines changed

2 files changed

+86
-34
lines changed

utils.go

+56-34
Original file line numberDiff line numberDiff line change
@@ -503,62 +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+
// 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+
506512
func formatBinaryDateTime(src []byte, withTime bool) (driver.Value, error) {
507-
const zeroDateTimeMicros = "0000-00-00 00:00:00.000000"
513+
if len(src) == 0 {
514+
if withTime {
515+
return zeroDateTime, nil
516+
}
517+
return zeroDateTime[:10], nil
518+
}
508519
var dst []byte
509520
if withTime {
510521
if len(src) == 11 {
511-
dst = []byte(zeroDateTimeMicros)
522+
dst = []byte("0000-00-00 00:00:00.000000")
512523
} else {
513-
dst = []byte(zeroDateTimeMicros[:19])
524+
dst = []byte("0000-00-00 00:00:00")
514525
}
515526
} else {
516-
dst = []byte(zeroDateTimeMicros[:10])
527+
dst = []byte("0000-00-00")
517528
}
518529
switch len(src) {
519530
case 11:
520531
microsecs := binary.LittleEndian.Uint32(src[7:11])
521-
dst[20] += byte((microsecs / 100000) % 10)
522-
dst[21] += byte((microsecs / 10000) % 10)
523-
dst[22] += byte((microsecs / 1000) % 10)
524-
dst[23] += byte((microsecs / 100) % 10)
525-
dst[24] += byte((microsecs / 10) % 10)
526-
dst[25] += byte(microsecs % 10)
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)
527543
fallthrough
528544
case 7:
529-
hour := src[4]
530-
minute := src[5]
531545
second := src[6]
532-
dst[11] += (hour / 10) % 10
533-
dst[12] += hour % 10
534-
dst[14] += (minute / 10) % 10
535-
dst[15] += minute % 10
536-
dst[17] += (second / 10) % 10
537-
dst[18] += second % 10
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
538557
fallthrough
539558
case 4:
540-
year := binary.LittleEndian.Uint16(src[:2])
541-
month := src[2]
542559
day := src[3]
543-
dst[0] += byte((year / 1000) % 10)
544-
dst[1] += byte((year / 100) % 10)
545-
dst[2] += byte((year / 10) % 10)
546-
dst[3] += byte(year % 10)
547-
dst[5] += (month / 10) % 10
548-
dst[6] += month % 10
549-
dst[8] += (day / 10) % 10
550-
dst[9] += day % 10
551-
return dst, nil
552-
case 0:
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)
553575
return dst, nil
554576
}
555-
var mode string
577+
var t string
556578
if withTime {
557-
mode = "DATETIME"
579+
t = "DATETIME"
558580
} else {
559-
mode = "DATE"
581+
t = "DATE"
560582
}
561-
return nil, fmt.Errorf("invalid %s-packet length %d", mode, len(src))
583+
return nil, fmt.Errorf("invalid %s-packet length %d", t, len(src))
562584
}
563585

564586
/******************************************************************************

utils_test.go

+30
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)