Skip to content

Commit c81c7bf

Browse files
chanxuehongtz70s
authored andcommitted
performance improvement for time format (go-sql-driver#1118)
1 parent 646ba62 commit c81c7bf

File tree

4 files changed

+129
-39
lines changed

4 files changed

+129
-39
lines changed

connection.go

+4-38
Original file line numberDiff line numberDiff line change
@@ -245,44 +245,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
245245
if v.IsZero() {
246246
buf = append(buf, "'0000-00-00'"...)
247247
} else {
248-
v := v.In(mc.cfg.Loc)
249-
v = v.Add(time.Nanosecond * 500) // To round under microsecond
250-
year := v.Year()
251-
year100 := year / 100
252-
year1 := year % 100
253-
month := v.Month()
254-
day := v.Day()
255-
hour := v.Hour()
256-
minute := v.Minute()
257-
second := v.Second()
258-
micro := v.Nanosecond() / 1000
259-
260-
buf = append(buf, []byte{
261-
'\'',
262-
digits10[year100], digits01[year100],
263-
digits10[year1], digits01[year1],
264-
'-',
265-
digits10[month], digits01[month],
266-
'-',
267-
digits10[day], digits01[day],
268-
' ',
269-
digits10[hour], digits01[hour],
270-
':',
271-
digits10[minute], digits01[minute],
272-
':',
273-
digits10[second], digits01[second],
274-
}...)
275-
276-
if micro != 0 {
277-
micro10000 := micro / 10000
278-
micro100 := micro / 100 % 100
279-
micro1 := micro % 100
280-
buf = append(buf, []byte{
281-
'.',
282-
digits10[micro10000], digits01[micro10000],
283-
digits10[micro100], digits01[micro100],
284-
digits10[micro1], digits01[micro1],
285-
}...)
248+
buf = append(buf, '\'')
249+
buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
250+
if err != nil {
251+
return "", err
286252
}
287253
buf = append(buf, '\'')
288254
}

packets.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
11161116
if v.IsZero() {
11171117
b = append(b, "0000-00-00"...)
11181118
} else {
1119-
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
1119+
b, err = appendDateTime(b, v.In(mc.cfg.Loc))
1120+
if err != nil {
1121+
return err
1122+
}
11201123
}
11211124

11221125
paramValues = appendLengthEncodedInteger(paramValues,

utils.go

+49
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,55 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
276276
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
277277
}
278278

279+
func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
280+
nsec := t.Nanosecond()
281+
// to round under microsecond
282+
if nsec%1000 >= 500 { // save half of time.Time.Add calls
283+
t = t.Add(500 * time.Nanosecond)
284+
nsec = t.Nanosecond()
285+
}
286+
year, month, day := t.Date()
287+
hour, min, sec := t.Clock()
288+
micro := nsec / 1000
289+
290+
if year < 1 || year > 9999 {
291+
return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
292+
}
293+
year100 := year / 100
294+
year1 := year % 100
295+
296+
var localBuf [26]byte // does not escape
297+
localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1]
298+
localBuf[4] = '-'
299+
localBuf[5], localBuf[6] = digits10[month], digits01[month]
300+
localBuf[7] = '-'
301+
localBuf[8], localBuf[9] = digits10[day], digits01[day]
302+
303+
if hour == 0 && min == 0 && sec == 0 && micro == 0 {
304+
return append(buf, localBuf[:10]...), nil
305+
}
306+
307+
localBuf[10] = ' '
308+
localBuf[11], localBuf[12] = digits10[hour], digits01[hour]
309+
localBuf[13] = ':'
310+
localBuf[14], localBuf[15] = digits10[min], digits01[min]
311+
localBuf[16] = ':'
312+
localBuf[17], localBuf[18] = digits10[sec], digits01[sec]
313+
314+
if micro == 0 {
315+
return append(buf, localBuf[:19]...), nil
316+
}
317+
318+
micro10000 := micro / 10000
319+
micro100 := (micro / 100) % 100
320+
micro1 := micro % 100
321+
localBuf[19] = '.'
322+
localBuf[20], localBuf[21], localBuf[22], localBuf[23], localBuf[24], localBuf[25] =
323+
digits10[micro10000], digits01[micro10000], digits10[micro100], digits01[micro100], digits10[micro1], digits01[micro1]
324+
325+
return append(buf, localBuf[:]...), nil
326+
}
327+
279328
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
280329
// if the DATE or DATETIME has the zero value.
281330
// It must never be changed.

utils_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,78 @@ func TestIsolationLevelMapping(t *testing.T) {
293293
}
294294
}
295295

296+
func TestAppendDateTime(t *testing.T) {
297+
tests := []struct {
298+
t time.Time
299+
str string
300+
}{
301+
{
302+
t: time.Date(2020, 05, 30, 0, 0, 0, 0, time.UTC),
303+
str: "2020-05-30",
304+
},
305+
{
306+
t: time.Date(2020, 05, 30, 22, 0, 0, 0, time.UTC),
307+
str: "2020-05-30 22:00:00",
308+
},
309+
{
310+
t: time.Date(2020, 05, 30, 22, 33, 0, 0, time.UTC),
311+
str: "2020-05-30 22:33:00",
312+
},
313+
{
314+
t: time.Date(2020, 05, 30, 22, 33, 44, 0, time.UTC),
315+
str: "2020-05-30 22:33:44",
316+
},
317+
{
318+
t: time.Date(2020, 05, 30, 22, 33, 44, 550000000, time.UTC),
319+
str: "2020-05-30 22:33:44.550000",
320+
},
321+
{
322+
t: time.Date(2020, 05, 30, 22, 33, 44, 550000499, time.UTC),
323+
str: "2020-05-30 22:33:44.550000",
324+
},
325+
{
326+
t: time.Date(2020, 05, 30, 22, 33, 44, 550000500, time.UTC),
327+
str: "2020-05-30 22:33:44.550001",
328+
},
329+
{
330+
t: time.Date(2020, 05, 30, 22, 33, 44, 550000567, time.UTC),
331+
str: "2020-05-30 22:33:44.550001",
332+
},
333+
{
334+
t: time.Date(2020, 05, 30, 22, 33, 44, 999999567, time.UTC),
335+
str: "2020-05-30 22:33:45",
336+
},
337+
}
338+
for _, v := range tests {
339+
buf := make([]byte, 0, 32)
340+
buf, _ = appendDateTime(buf, v.t)
341+
if str := string(buf); str != v.str {
342+
t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
343+
return
344+
}
345+
}
346+
347+
// year out of range
348+
{
349+
v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
350+
buf := make([]byte, 0, 32)
351+
_, err := appendDateTime(buf, v)
352+
if err == nil {
353+
t.Error("want an error")
354+
return
355+
}
356+
}
357+
{
358+
v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
359+
buf := make([]byte, 0, 32)
360+
_, err := appendDateTime(buf, v)
361+
if err == nil {
362+
t.Error("want an error")
363+
return
364+
}
365+
}
366+
}
367+
296368
func TestParseDateTime(t *testing.T) {
297369
cases := []struct {
298370
name string

0 commit comments

Comments
 (0)