Skip to content

Commit 4a082cf

Browse files
serprexlance6716
andauthored
replication: return string when datetime's day or month is 00 (#1047)
* replication: return string when datetime's day or month is 00 MySQL supports 0 for day/month when this is an unknown value This cannot be encoded into a time.Time, where time.Date normalizes these 0s (so 2000-00-00 would become 1999-11-30) Pass these values through in their raw string form instead * feedback * oops --------- Co-authored-by: lance6716 <[email protected]>
1 parent bf1f86b commit 4a082cf

File tree

3 files changed

+47
-31
lines changed

3 files changed

+47
-31
lines changed

replication/row_event.go

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,7 +1316,7 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16, isPartial boo
13161316
n = 4
13171317
t := binary.LittleEndian.Uint32(data)
13181318
if t == 0 {
1319-
v = formatZeroTime(0, 0)
1319+
v = "0000-00-00 00:00:00"
13201320
} else {
13211321
v = e.parseFracTime(fracTime{
13221322
Time: time.Unix(int64(t), 0),
@@ -1331,26 +1331,37 @@ func (e *RowsEvent) decodeValue(data []byte, tp byte, meta uint16, isPartial boo
13311331
n = 8
13321332
i64 := binary.LittleEndian.Uint64(data)
13331333
if i64 == 0 {
1334-
v = formatZeroTime(0, 0)
1334+
v = "0000-00-00 00:00:00"
13351335
} else {
13361336
d := i64 / 1000000
13371337
t := i64 % 1000000
1338-
v = e.parseFracTime(fracTime{
1339-
Time: time.Date(
1340-
int(d/10000),
1341-
time.Month((d%10000)/100),
1342-
int(d%100),
1343-
int(t/10000),
1344-
int((t%10000)/100),
1345-
int(t%100),
1346-
0,
1347-
time.UTC,
1348-
),
1349-
Dec: 0,
1350-
})
1338+
years := int(d / 10000)
1339+
months := int(d%10000) / 100
1340+
days := int(d % 100)
1341+
hours := int(t / 10000)
1342+
minutes := int(t%10000) / 100
1343+
seconds := int(t % 100)
1344+
if !e.parseTime || months == 0 || days == 0 {
1345+
v = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
1346+
years, months, days, hours, minutes, seconds)
1347+
} else {
1348+
v = e.parseFracTime(fracTime{
1349+
Time: time.Date(
1350+
years,
1351+
time.Month(months),
1352+
days,
1353+
hours,
1354+
minutes,
1355+
seconds,
1356+
0,
1357+
time.UTC,
1358+
),
1359+
Dec: 0,
1360+
})
1361+
}
13511362
}
13521363
case mysql.MYSQL_TYPE_DATETIME2:
1353-
v, n, err = decodeDatetime2(data, meta)
1364+
v, n, err = decodeDatetime2(data, meta, e.parseTime)
13541365
v = e.parseFracTime(v)
13551366
case mysql.MYSQL_TYPE_TIME:
13561367
n = 3
@@ -1675,7 +1686,7 @@ func decodeTimestamp2(data []byte, dec uint16, timestampStringLocation *time.Loc
16751686

16761687
const DATETIMEF_INT_OFS int64 = 0x8000000000
16771688

1678-
func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
1689+
func decodeDatetime2(data []byte, dec uint16, parseTime bool) (interface{}, int, error) {
16791690
// get datetime binary length
16801691
n := int(5 + (dec+1)/2)
16811692

@@ -1725,8 +1736,8 @@ func decodeDatetime2(data []byte, dec uint16) (interface{}, int, error) {
17251736
// minute = 0 = 0b000000
17261737
// second = 0 = 0b000000
17271738
// integer value = 0b1100100000010110000100000000000000000 = 107420450816
1728-
if intPart < 107420450816 {
1729-
return formatBeforeUnixZeroTime(year, month, day, hour, minute, second, int(frac), int(dec)), n, nil
1739+
if !parseTime || intPart < 107420450816 || month == 0 || day == 0 {
1740+
return formatDatetime(year, month, day, hour, minute, second, int(frac), int(dec)), n, nil
17301741
}
17311742

17321743
return fracTime{

replication/row_event_test.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,8 @@ func TestDecodeDatetime2(t *testing.T) {
675675
}{
676676
{[]byte("\xfe\xf3\xff\x7e\xfb"), 0, true, "9999-12-31 23:59:59"},
677677
{[]byte("\x99\x9a\xb8\xf7\xaa"), 0, true, "2016-10-28 15:30:42"},
678+
{[]byte("\x99\x98\x38\xf7\xaa"), 0, false, "2016-00-28 15:30:42"},
679+
{[]byte("\x99\x9a\x80\xf7\xaa"), 0, false, "2016-10-00 15:30:42"},
678680
{[]byte("\x99\x02\xc2\x00\x00"), 0, true, "1970-01-01 00:00:00"},
679681
{[]byte("\x80\x00\x00\x00\x00"), 0, false, "0000-00-00 00:00:00"},
680682
{[]byte("\x80\x00\x02\xf1\x05"), 0, false, "0000-00-01 15:04:05"},
@@ -684,17 +686,20 @@ func TestDecodeDatetime2(t *testing.T) {
684686
{[]byte("\x80\x03\x82\x00\x00\x01\xe2\x40"), uint16(6), false, "0001-01-01 00:00:00.123456"},
685687
}
686688
for _, tc := range testcases {
687-
value, _, err := decodeDatetime2(tc.data, tc.dec)
688-
require.NoError(t, err)
689-
switch v := value.(type) {
690-
case fracTime:
691-
require.True(t, tc.getFracTime)
692-
require.Equal(t, tc.expected, v.String())
693-
case string:
694-
require.False(t, tc.getFracTime)
695-
require.Equal(t, tc.expected, v)
696-
default:
697-
require.FailNow(t, "invalid value type: %T", value)
689+
for _, parseTime := range []bool{true, false} {
690+
value, _, err := decodeDatetime2(tc.data, tc.dec, parseTime)
691+
require.NoError(t, err)
692+
switch v := value.(type) {
693+
case fracTime:
694+
require.True(t, parseTime)
695+
require.True(t, tc.getFracTime)
696+
require.Equal(t, tc.expected, v.String())
697+
case string:
698+
require.False(t, parseTime && tc.getFracTime)
699+
require.Equal(t, tc.expected, v)
700+
default:
701+
require.FailNow(t, "invalid value type: %T", value)
702+
}
698703
}
699704
}
700705
}

replication/time.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func formatZeroTime(frac int, dec int) string {
4444
return s[0 : len(s)-(6-dec)]
4545
}
4646

47-
func formatBeforeUnixZeroTime(year, month, day, hour, minute, second, frac, dec int) string {
47+
func formatDatetime(year, month, day, hour, minute, second, frac, dec int) string {
4848
if dec == 0 {
4949
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
5050
}

0 commit comments

Comments
 (0)