Browse Source

applied modulo and allocation optimizations, added test

Arne Hormann 12 years ago
parent
commit
ba3e6d4043
2 changed files with 86 additions and 34 deletions
  1. 56 34
      utils.go
  2. 30 0
      utils_test.go

+ 56 - 34
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))
 }
 
 /******************************************************************************

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