Skip to content

Commit 2307b45

Browse files
authored
Fix TIME format for binary columns (go-sql-driver#818)
1 parent d523deb commit 2307b45

File tree

3 files changed

+157
-106
lines changed

3 files changed

+157
-106
lines changed

packets.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1261,7 +1261,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
12611261
rows.rs.columns[i].decimals,
12621262
)
12631263
}
1264-
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
1264+
dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
12651265
case rows.mc.parseTime:
12661266
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
12671267
default:
@@ -1281,7 +1281,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
12811281
)
12821282
}
12831283
}
1284-
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
1284+
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
12851285
}
12861286

12871287
if err == nil {

utils.go

+118-102
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"encoding/binary"
1515
"fmt"
1616
"io"
17+
"strconv"
1718
"strings"
1819
"sync"
1920
"sync/atomic"
@@ -227,141 +228,156 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
227228
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
228229
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
229230

230-
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
231-
// length expects the deterministic length of the zero value,
232-
// negative time and 100+ hours are automatically added if needed
233-
if len(src) == 0 {
234-
if justTime {
235-
return zeroDateTime[11 : 11+length], nil
236-
}
237-
return zeroDateTime[:length], nil
231+
func appendMicrosecs(dst, src []byte, decimals int) []byte {
232+
if decimals <= 0 {
233+
return dst
238234
}
239-
var dst []byte // return value
240-
var pt, p1, p2, p3 byte // current digit pair
241-
var zOffs byte // offset of value in zeroDateTime
242-
if justTime {
243-
switch length {
244-
case
245-
8, // time (can be up to 10 when negative and 100+ hours)
246-
10, 11, 12, 13, 14, 15: // time with fractional seconds
247-
default:
248-
return nil, fmt.Errorf("illegal TIME length %d", length)
249-
}
250-
switch len(src) {
251-
case 8, 12:
252-
default:
253-
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
254-
}
255-
// +2 to enable negative time and 100+ hours
256-
dst = make([]byte, 0, length+2)
257-
if src[0] == 1 {
258-
dst = append(dst, '-')
259-
}
260-
if src[1] != 0 {
261-
hour := uint16(src[1])*24 + uint16(src[5])
262-
pt = byte(hour / 100)
263-
p1 = byte(hour - 100*uint16(pt))
264-
dst = append(dst, digits01[pt])
265-
} else {
266-
p1 = src[5]
267-
}
268-
zOffs = 11
269-
src = src[6:]
270-
} else {
271-
switch length {
272-
case 10, 19, 21, 22, 23, 24, 25, 26:
273-
default:
274-
t := "DATE"
275-
if length > 10 {
276-
t += "TIME"
277-
}
278-
return nil, fmt.Errorf("illegal %s length %d", t, length)
279-
}
280-
switch len(src) {
281-
case 4, 7, 11:
282-
default:
283-
t := "DATE"
284-
if length > 10 {
285-
t += "TIME"
286-
}
287-
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
288-
}
289-
dst = make([]byte, 0, length)
290-
// start with the date
291-
year := binary.LittleEndian.Uint16(src[:2])
292-
pt = byte(year / 100)
293-
p1 = byte(year - 100*uint16(pt))
294-
p2, p3 = src[2], src[3]
295-
dst = append(dst,
296-
digits10[pt], digits01[pt],
297-
digits10[p1], digits01[p1], '-',
298-
digits10[p2], digits01[p2], '-',
299-
digits10[p3], digits01[p3],
300-
)
301-
if length == 10 {
302-
return dst, nil
303-
}
304-
if len(src) == 4 {
305-
return append(dst, zeroDateTime[10:length]...), nil
306-
}
307-
dst = append(dst, ' ')
308-
p1 = src[4] // hour
309-
src = src[5:]
310-
}
311-
// p1 is 2-digit hour, src is after hour
312-
p2, p3 = src[0], src[1]
313-
dst = append(dst,
314-
digits10[p1], digits01[p1], ':',
315-
digits10[p2], digits01[p2], ':',
316-
digits10[p3], digits01[p3],
317-
)
318-
if length <= byte(len(dst)) {
319-
return dst, nil
320-
}
321-
src = src[2:]
322235
if len(src) == 0 {
323-
return append(dst, zeroDateTime[19:zOffs+length]...), nil
236+
return append(dst, ".000000"[:decimals+1]...)
324237
}
238+
325239
microsecs := binary.LittleEndian.Uint32(src[:4])
326-
p1 = byte(microsecs / 10000)
240+
p1 := byte(microsecs / 10000)
327241
microsecs -= 10000 * uint32(p1)
328-
p2 = byte(microsecs / 100)
242+
p2 := byte(microsecs / 100)
329243
microsecs -= 100 * uint32(p2)
330-
p3 = byte(microsecs)
331-
switch decimals := zOffs + length - 20; decimals {
244+
p3 := byte(microsecs)
245+
246+
switch decimals {
332247
default:
333248
return append(dst, '.',
334249
digits10[p1], digits01[p1],
335250
digits10[p2], digits01[p2],
336251
digits10[p3], digits01[p3],
337-
), nil
252+
)
338253
case 1:
339254
return append(dst, '.',
340255
digits10[p1],
341-
), nil
256+
)
342257
case 2:
343258
return append(dst, '.',
344259
digits10[p1], digits01[p1],
345-
), nil
260+
)
346261
case 3:
347262
return append(dst, '.',
348263
digits10[p1], digits01[p1],
349264
digits10[p2],
350-
), nil
265+
)
351266
case 4:
352267
return append(dst, '.',
353268
digits10[p1], digits01[p1],
354269
digits10[p2], digits01[p2],
355-
), nil
270+
)
356271
case 5:
357272
return append(dst, '.',
358273
digits10[p1], digits01[p1],
359274
digits10[p2], digits01[p2],
360275
digits10[p3],
361-
), nil
276+
)
362277
}
363278
}
364279

280+
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
281+
// length expects the deterministic length of the zero value,
282+
// negative time and 100+ hours are automatically added if needed
283+
if len(src) == 0 {
284+
return zeroDateTime[:length], nil
285+
}
286+
var dst []byte // return value
287+
var p1, p2, p3 byte // current digit pair
288+
289+
switch length {
290+
case 10, 19, 21, 22, 23, 24, 25, 26:
291+
default:
292+
t := "DATE"
293+
if length > 10 {
294+
t += "TIME"
295+
}
296+
return nil, fmt.Errorf("illegal %s length %d", t, length)
297+
}
298+
switch len(src) {
299+
case 4, 7, 11:
300+
default:
301+
t := "DATE"
302+
if length > 10 {
303+
t += "TIME"
304+
}
305+
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
306+
}
307+
dst = make([]byte, 0, length)
308+
// start with the date
309+
year := binary.LittleEndian.Uint16(src[:2])
310+
pt := year / 100
311+
p1 = byte(year - 100*uint16(pt))
312+
p2, p3 = src[2], src[3]
313+
dst = append(dst,
314+
digits10[pt], digits01[pt],
315+
digits10[p1], digits01[p1], '-',
316+
digits10[p2], digits01[p2], '-',
317+
digits10[p3], digits01[p3],
318+
)
319+
if length == 10 {
320+
return dst, nil
321+
}
322+
if len(src) == 4 {
323+
return append(dst, zeroDateTime[10:length]...), nil
324+
}
325+
dst = append(dst, ' ')
326+
p1 = src[4] // hour
327+
src = src[5:]
328+
329+
// p1 is 2-digit hour, src is after hour
330+
p2, p3 = src[0], src[1]
331+
dst = append(dst,
332+
digits10[p1], digits01[p1], ':',
333+
digits10[p2], digits01[p2], ':',
334+
digits10[p3], digits01[p3],
335+
)
336+
return appendMicrosecs(dst, src[2:], int(length)-20), nil
337+
}
338+
339+
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
340+
// length expects the deterministic length of the zero value,
341+
// negative time and 100+ hours are automatically added if needed
342+
if len(src) == 0 {
343+
return zeroDateTime[11 : 11+length], nil
344+
}
345+
var dst []byte // return value
346+
347+
switch length {
348+
case
349+
8, // time (can be up to 10 when negative and 100+ hours)
350+
10, 11, 12, 13, 14, 15: // time with fractional seconds
351+
default:
352+
return nil, fmt.Errorf("illegal TIME length %d", length)
353+
}
354+
switch len(src) {
355+
case 8, 12:
356+
default:
357+
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
358+
}
359+
// +2 to enable negative time and 100+ hours
360+
dst = make([]byte, 0, length+2)
361+
if src[0] == 1 {
362+
dst = append(dst, '-')
363+
}
364+
days := binary.LittleEndian.Uint32(src[1:5])
365+
hours := int64(days)*24 + int64(src[5])
366+
367+
if hours >= 100 {
368+
dst = strconv.AppendInt(dst, hours, 10)
369+
} else {
370+
dst = append(dst, digits10[hours], digits01[hours])
371+
}
372+
373+
min, sec := src[6], src[7]
374+
dst = append(dst, ':',
375+
digits10[min], digits01[min], ':',
376+
digits10[sec], digits01[sec],
377+
)
378+
return appendMicrosecs(dst, src[8:], int(length)-9), nil
379+
}
380+
365381
/******************************************************************************
366382
* Convert from and to bytes *
367383
******************************************************************************/

utils_test.go

+37-2
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ func TestFormatBinaryDateTime(t *testing.T) {
102102
rawDate[6] = 23 // seconds
103103
binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
104104
expect := func(expected string, inlen, outlen uint8) {
105-
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
105+
actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen)
106106
bytes, ok := actual.([]byte)
107107
if !ok {
108108
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
109109
}
110110
if string(bytes) != expected {
111111
t.Errorf(
112112
"expected %q, got %q for length in %d, out %d",
113-
bytes, actual, inlen, outlen,
113+
expected, actual, inlen, outlen,
114114
)
115115
}
116116
}
@@ -121,6 +121,41 @@ func TestFormatBinaryDateTime(t *testing.T) {
121121
expect("1978-12-30 15:46:23.987654", 11, 26)
122122
}
123123

124+
func TestFormatBinaryTime(t *testing.T) {
125+
expect := func(expected string, src []byte, outlen uint8) {
126+
actual, _ := formatBinaryTime(src, outlen)
127+
bytes, ok := actual.([]byte)
128+
if !ok {
129+
t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
130+
}
131+
if string(bytes) != expected {
132+
t.Errorf(
133+
"expected %q, got %q for src=%q and outlen=%d",
134+
expected, actual, src, outlen)
135+
}
136+
}
137+
138+
// binary format:
139+
// sign (0: positive, 1: negative), days(4), hours, minutes, seconds, micro(4)
140+
141+
// Zeros
142+
expect("00:00:00", []byte{}, 8)
143+
expect("00:00:00.0", []byte{}, 10)
144+
expect("00:00:00.000000", []byte{}, 15)
145+
146+
// Without micro(4)
147+
expect("12:34:56", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 8)
148+
expect("-12:34:56", []byte{1, 0, 0, 0, 0, 12, 34, 56}, 8)
149+
expect("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 11)
150+
expect("24:34:56", []byte{0, 1, 0, 0, 0, 0, 34, 56}, 8)
151+
expect("-99:34:56", []byte{1, 4, 0, 0, 0, 3, 34, 56}, 8)
152+
expect("103079215103:34:56", []byte{0, 255, 255, 255, 255, 23, 34, 56}, 8)
153+
154+
// With micro(4)
155+
expect("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56, 99, 0, 0, 0}, 11)
156+
expect("12:34:56.000099", []byte{0, 0, 0, 0, 0, 12, 34, 56, 99, 0, 0, 0}, 15)
157+
}
158+
124159
func TestEscapeBackslash(t *testing.T) {
125160
expect := func(expected, value string) {
126161
actual := string(escapeBytesBackslash([]byte{}, []byte(value)))

0 commit comments

Comments
 (0)