Skip to content

Commit ee43fdd

Browse files
committed
Fix TIME format for binary columns
fixes #817
1 parent d523deb commit ee43fdd

File tree

3 files changed

+171
-78
lines changed

3 files changed

+171
-78
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

+134-76
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,87 +228,55 @@ 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+
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
231232
// length expects the deterministic length of the zero value,
232233
// negative time and 100+ hours are automatically added if needed
233234
if len(src) == 0 {
234-
if justTime {
235-
return zeroDateTime[11 : 11+length], nil
236-
}
237235
return zeroDateTime[:length], nil
238236
}
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
237+
var dst []byte // return value
238+
var p1, p2, p3 byte // current digit pair
239+
240+
switch length {
241+
case 10, 19, 21, 22, 23, 24, 25, 26:
242+
default:
243+
t := "DATE"
244+
if length > 10 {
245+
t += "TIME"
303246
}
304-
if len(src) == 4 {
305-
return append(dst, zeroDateTime[10:length]...), nil
247+
return nil, fmt.Errorf("illegal %s length %d", t, length)
248+
}
249+
switch len(src) {
250+
case 4, 7, 11:
251+
default:
252+
t := "DATE"
253+
if length > 10 {
254+
t += "TIME"
306255
}
307-
dst = append(dst, ' ')
308-
p1 = src[4] // hour
309-
src = src[5:]
256+
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
257+
}
258+
dst = make([]byte, 0, length)
259+
// start with the date
260+
year := binary.LittleEndian.Uint16(src[:2])
261+
pt := year / 100
262+
p1 = byte(year - 100*uint16(pt))
263+
p2, p3 = src[2], src[3]
264+
dst = append(dst,
265+
digits10[pt], digits01[pt],
266+
digits10[p1], digits01[p1], '-',
267+
digits10[p2], digits01[p2], '-',
268+
digits10[p3], digits01[p3],
269+
)
270+
if length == 10 {
271+
return dst, nil
310272
}
273+
if len(src) == 4 {
274+
return append(dst, zeroDateTime[10:length]...), nil
275+
}
276+
dst = append(dst, ' ')
277+
p1 = src[4] // hour
278+
src = src[5:]
279+
311280
// p1 is 2-digit hour, src is after hour
312281
p2, p3 = src[0], src[1]
313282
dst = append(dst,
@@ -320,15 +289,104 @@ func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value
320289
}
321290
src = src[2:]
322291
if len(src) == 0 {
323-
return append(dst, zeroDateTime[19:zOffs+length]...), nil
292+
return append(dst, zeroDateTime[19:length]...), nil
293+
}
294+
microsecs := binary.LittleEndian.Uint32(src[:4])
295+
p1 = byte(microsecs / 10000)
296+
microsecs -= 10000 * uint32(p1)
297+
p2 = byte(microsecs / 100)
298+
microsecs -= 100 * uint32(p2)
299+
p3 = byte(microsecs)
300+
switch decimals := length - 20; decimals {
301+
default:
302+
return append(dst, '.',
303+
digits10[p1], digits01[p1],
304+
digits10[p2], digits01[p2],
305+
digits10[p3], digits01[p3],
306+
), nil
307+
case 1:
308+
return append(dst, '.',
309+
digits10[p1],
310+
), nil
311+
case 2:
312+
return append(dst, '.',
313+
digits10[p1], digits01[p1],
314+
), nil
315+
case 3:
316+
return append(dst, '.',
317+
digits10[p1], digits01[p1],
318+
digits10[p2],
319+
), nil
320+
case 4:
321+
return append(dst, '.',
322+
digits10[p1], digits01[p1],
323+
digits10[p2], digits01[p2],
324+
), nil
325+
case 5:
326+
return append(dst, '.',
327+
digits10[p1], digits01[p1],
328+
digits10[p2], digits01[p2],
329+
digits10[p3],
330+
), nil
331+
}
332+
}
333+
334+
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
335+
// length expects the deterministic length of the zero value,
336+
// negative time and 100+ hours are automatically added if needed
337+
if len(src) == 0 {
338+
return zeroDateTime[11 : 11+length], nil
339+
}
340+
var dst []byte // return value
341+
342+
switch length {
343+
case
344+
8, // time (can be up to 10 when negative and 100+ hours)
345+
10, 11, 12, 13, 14, 15: // time with fractional seconds
346+
default:
347+
return nil, fmt.Errorf("illegal TIME length %d", length)
348+
}
349+
switch len(src) {
350+
case 8, 12:
351+
default:
352+
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
353+
}
354+
// +2 to enable negative time and 100+ hours
355+
dst = make([]byte, 0, length+2)
356+
if src[0] == 1 {
357+
dst = append(dst, '-')
358+
}
359+
days := binary.LittleEndian.Uint32(src[1:5])
360+
hours := int64(days)*24 + int64(src[5])
361+
362+
if hours >= 100 {
363+
dst = strconv.AppendInt(dst, hours, 10)
364+
} else {
365+
dst = append(dst, digits10[hours], digits01[hours])
366+
}
367+
368+
min, sec := src[6], src[7]
369+
dst = append(dst,
370+
digits10[min], digits01[min], ':',
371+
digits10[sec], digits01[sec],
372+
)
373+
374+
decimals := length - 9
375+
if decimals <= 0 {
376+
return dst, nil
377+
}
378+
379+
src = src[8:]
380+
if len(src) == 0 {
381+
return append(dst, zeroDateTime[19:length]...), nil
324382
}
325383
microsecs := binary.LittleEndian.Uint32(src[:4])
326384
p1 = byte(microsecs / 10000)
327385
microsecs -= 10000 * uint32(p1)
328386
p2 = byte(microsecs / 100)
329387
microsecs -= 100 * uint32(p2)
330388
p3 = byte(microsecs)
331-
switch decimals := zOffs + length - 20; decimals {
389+
switch decimals {
332390
default:
333391
return append(dst, '.',
334392
digits10[p1], digits01[p1],
@@ -425,7 +483,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
425483

426484
// Check data length
427485
if len(b) >= n {
428-
return b[n-int(num) : n : n], false, n, nil
486+
return b[n-int(num) : n:n], false, n, nil
429487
}
430488
return nil, false, n, io.EOF
431489
}

utils_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -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, _ := formatBinaryDateTime(src, outlen, true)
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+
bytes, 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+
expectTime("12:34:56", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 8)
148+
expectTime("-12:34:56", []byte{1, 0, 0, 0, 0, 12, 34, 56}, 8)
149+
expectTime("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 11)
150+
expectTime("24:34:56", []byte{0, 1, 0, 0, 0, 0, 34, 56}, 8)
151+
expectTime("-99:34:56", []byte{1, 4, 0, 0, 0, 3, 34, 56}, 8)
152+
expectTime("103079215103:34:56", []byte{0, 255, 255, 255, 255, 23, 34, 56}, 8)
153+
154+
// With micro(4)
155+
expectTime("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56, 99, 0, 0, 0}, 11)
156+
expectTime("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)