diff --git a/binlog/event_format_description.go b/binlog/event_format_description.go index 35d3f37..0a48ea2 100644 --- a/binlog/event_format_description.go +++ b/binlog/event_format_description.go @@ -54,7 +54,7 @@ const ( func (e *FormatDescriptionEvent) Decode(data []byte) error { buf := tools.NewBuffer(data) e.Version = buf.ReadUint16() - e.ServerVersion = trimString(buf.ReadStringVarLen(50)) + e.ServerVersion = trimStringEOF(buf.ReadStringVarLen(50)) e.CreateTimestamp = buf.ReadUint32() e.EventHeaderLength = buf.ReadUint8() e.EventTypeHeaderLengths = buf.ReadStringEOF() @@ -64,9 +64,6 @@ func (e *FormatDescriptionEvent) Decode(data []byte) error { ChecksumAlgorithm: ChecksumAlgorithmUndefined, } if e.ServerDetails.Version > 50601 { - // Last 5 bytes are: - // [1] Checksum algorithm - // [4] Checksum e.ServerDetails.ChecksumAlgorithm = ChecksumAlgorithm(data[len(data)-5]) e.EventTypeHeaderLengths = e.EventTypeHeaderLengths[:len(e.EventTypeHeaderLengths)-5] } @@ -127,7 +124,7 @@ func parseVersionNumber(v string) int { return major*10000 + minor*100 + patch } -func trimString(str []byte) string { +func trimStringEOF(str []byte) string { for i, c := range str { if c == 0x00 { return string(str[:i]) diff --git a/binlog/event_rotate.go b/binlog/event_rotate.go index 4400ad3..19879b2 100644 --- a/binlog/event_rotate.go +++ b/binlog/event_rotate.go @@ -2,7 +2,8 @@ package binlog import "github.com/localhots/bocadillo/tools" -// Position ... +// Position is a pair of log file name and a binary offset in it that is used to +// represent the beginning of the event description. type Position struct { File string Offset uint64 diff --git a/binlog/event_rows.go b/binlog/event_rows.go index 5ea075f..c1b5054 100644 --- a/binlog/event_rows.go +++ b/binlog/event_rows.go @@ -28,12 +28,7 @@ type RowsFlag uint16 const ( // RowsFlagEndOfStatement is used to clear old table mappings. - RowsFlagEndOfStatement RowsFlag = 0x0001 - rowsFlagNoForeignKeyChecks RowsFlag = 0x0002 - rowsFlagNoUniqueKeyChecks RowsFlag = 0x0004 - rowsFlagRowHasColumns RowsFlag = 0x0008 - - freeTableMapID = 0x00FFFFFF + RowsFlagEndOfStatement RowsFlag = 0x0001 ) // PeekTableIDAndFlags returns table ID and flags without decoding whole event. @@ -229,7 +224,7 @@ func (e *RowsEvent) decodeValue(buf *tools.Buffer, ct mysql.ColumnType, meta uin case mysql.ColumnTypeLongblob: return buf.ReadStringVarEnc(4) - // Bits + // Other case mysql.ColumnTypeBit: nbits := int(((meta >> 8) * 8) + (meta & 0xFF)) length = int(nbits+7) / 8 @@ -241,8 +236,6 @@ func (e *RowsEvent) decodeValue(buf *tools.Buffer, ct mysql.ColumnType, meta uin v, n := mysql.DecodeBit(buf.Cur(), nbits, length) buf.Skip(n) return v - - // Stuff case mysql.ColumnTypeEnum: return buf.ReadVarLen64(length) diff --git a/mysql/column_types.go b/mysql/column_types.go index 2906260..fa5df41 100644 --- a/mysql/column_types.go +++ b/mysql/column_types.go @@ -23,12 +23,12 @@ const ( ColumnTypeTime ColumnType = 0x0b ColumnTypeDatetime ColumnType = 0x0c ColumnTypeYear ColumnType = 0x0d - ColumnTypeNewDate ColumnType = 0x0e // Internal + ColumnTypeNewDate ColumnType = 0x0e ColumnTypeVarchar ColumnType = 0x0f ColumnTypeBit ColumnType = 0x10 - ColumnTypeTimestamp2 ColumnType = 0x11 // Internal - ColumnTypeDatetime2 ColumnType = 0x12 // Internal - ColumnTypeTime2 ColumnType = 0x13 // Internal + ColumnTypeTimestamp2 ColumnType = 0x11 + ColumnTypeDatetime2 ColumnType = 0x12 + ColumnTypeTime2 ColumnType = 0x13 ColumnTypeJSON ColumnType = 0xF5 ColumnTypeNewDecimal ColumnType = 0xF6 diff --git a/mysql/time.go b/mysql/time.go index ca103a9..3fd7595 100644 --- a/mysql/time.go +++ b/mysql/time.go @@ -9,12 +9,14 @@ import ( // Timezone is set for decoded datetime values. var Timezone = time.UTC -// DecodeYear ... +// DecodeYear decodes YEAR value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/year.html func DecodeYear(v uint8) uint16 { return uint16(v) + 1900 } -// DecodeDate ... +// DecodeDate decodes DATE value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/datetime.html func DecodeDate(v uint32) string { if v == 0 { return "0000-00-00" @@ -22,7 +24,8 @@ func DecodeDate(v uint32) string { return fmt.Sprintf("%04d-%02d-%02d", v/(16*32), v/32%16, v%32) } -// DecodeTime ... +// DecodeTime decodes TIME value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/time.html func DecodeTime(v uint32) string { if v == 0 { return "00:00:00" @@ -34,97 +37,12 @@ func DecodeTime(v uint32) string { return fmt.Sprintf("%s%02d:%02d:%02d", sign, v/10000, (v%10000)/100, v%100) } -// DecodeTimestamp2 ... -// Implementation borrowed from https://github.com/siddontang/go-mysql/ -func DecodeTimestamp2(data []byte, dec uint16) (string, int) { - //get timestamp binary length - n := int(4 + (dec+1)/2) - sec := int64(binary.BigEndian.Uint32(data[0:4])) - usec := int64(0) - switch dec { - case 1, 2: - usec = int64(data[4]) * 10000 - case 3, 4: - usec = int64(binary.BigEndian.Uint16(data[4:])) * 100 - case 5, 6: - usec = int64(DecodeVarLen64BigEndian(data[4:7])) - } - - if sec == 0 { - return formatZeroTime(int(usec), int(dec)), n - } - - return FracTime{time.Unix(sec, usec*1000), int(dec)}.String(), n -} - -// DecodeDatetime ... -func DecodeDatetime(v uint64) string { - d := v / 1000000 - t := v % 1000000 - return FracTime{Time: time.Date(int(d/10000), - time.Month((d%10000)/100), - int(d%100), - int(t/10000), - int((t%10000)/100), - int(t%100), - 0, - Timezone, - )}.String() -} - -// DecodeDatetime2 ... -// Implementation borrowed from https://github.com/siddontang/go-mysql/ -func DecodeDatetime2(data []byte, dec uint16) (string, int) { - const offset int64 = 0x8000000000 - //get datetime binary length - n := int(5 + (dec+1)/2) - - intPart := int64(DecodeVarLen64BigEndian(data[0:5])) - offset - var frac int64 - - switch dec { - case 1, 2: - frac = int64(data[5]) * 10000 - case 3, 4: - frac = int64(binary.BigEndian.Uint16(data[5:7])) * 100 - case 5, 6: - frac = int64(DecodeVarLen64BigEndian(data[5:8])) - } - - if intPart == 0 { - return formatZeroTime(int(frac), int(dec)), n - } - - tmp := intPart<<24 + frac - //handle sign??? - if tmp < 0 { - tmp = -tmp - } - - // var secPart int64 = tmp % (1 << 24) - ymdhms := tmp >> 24 - - ymd := ymdhms >> 17 - ym := ymd >> 5 - hms := ymdhms % (1 << 17) - - day := int(ymd % (1 << 5)) - month := int(ym % 13) - year := int(ym / 13) - - second := int(hms % (1 << 6)) - minute := int((hms >> 6) % (1 << 6)) - hour := int((hms >> 12)) - - return FracTime{time.Date(year, time.Month(month), day, hour, minute, second, int(frac*1000), Timezone), int(dec)}.String(), n -} - -// DecodeTime2 ... +// DecodeTime2 decodes TIME v2 value. // Implementation borrowed from https://github.com/siddontang/go-mysql/ func DecodeTime2(data []byte, dec uint16) (string, int) { const offset int64 = 0x800000000000 const intOffset int64 = 0x800000 - //time binary length + // time binary length n := int(3 + (dec+1)/2) tmp := int64(0) @@ -136,8 +54,8 @@ func DecodeTime2(data []byte, dec uint16) (string, int) { intPart = int64(DecodeVarLen64BigEndian(data[0:3])) - intOffset frac = int64(data[3]) if intPart < 0 && frac > 0 { - intPart++ /* Shift to the next integer value */ - frac -= 0x100 /* -(0x100 - frac) */ + intPart++ // Shift to the next integer value + frac -= 0x100 // -(0x100 - frac) } tmp = intPart<<24 + frac*10000 case 3: @@ -145,12 +63,10 @@ func DecodeTime2(data []byte, dec uint16) (string, int) { intPart = int64(DecodeVarLen64BigEndian(data[0:3])) - intOffset frac = int64(binary.BigEndian.Uint16(data[3:5])) if intPart < 0 && frac > 0 { - /* - Fix reverse fractional part order: "0x10000 - frac". - See comments for FSP=1 and FSP=2 above. - */ - intPart++ /* Shift to the next integer value */ - frac -= 0x10000 /* -(0x10000-frac) */ + // Fix reverse fractional part order: "0x10000 - frac". + // See comments for FSP=1 and FSP=2 above. + intPart++ // Shift to the next integer value + frac -= 0x10000 // -(0x10000-frac) } tmp = intPart<<24 + frac*100 @@ -175,9 +91,9 @@ func DecodeTime2(data []byte, dec uint16) (string, int) { hms = tmp >> 24 - hour := (hms >> 12) % (1 << 10) /* 10 bits starting at 12th */ - minute := (hms >> 6) % (1 << 6) /* 6 bits starting at 6th */ - second := hms % (1 << 6) /* 6 bits starting at 0th */ + hour := (hms >> 12) % (1 << 10) // 10 bits starting at 12th + minute := (hms >> 6) % (1 << 6) // 6 bits starting at 6th + second := hms % (1 << 6) // 6 bits starting at 0th secPart := tmp % (1 << 24) if secPart != 0 { @@ -187,6 +103,94 @@ func DecodeTime2(data []byte, dec uint16) (string, int) { return fmt.Sprintf("%s%02d:%02d:%02d", sign, hour, minute, second), n } +// DecodeTimestamp2 decodes TIMESTAMP v2 value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/datetime.html +// Implementation borrowed from https://github.com/siddontang/go-mysql/ +func DecodeTimestamp2(data []byte, dec uint16) (string, int) { + // get timestamp binary length + n := int(4 + (dec+1)/2) + sec := int64(binary.BigEndian.Uint32(data[0:4])) + usec := int64(0) + switch dec { + case 1, 2: + usec = int64(data[4]) * 10000 + case 3, 4: + usec = int64(binary.BigEndian.Uint16(data[4:])) * 100 + case 5, 6: + usec = int64(DecodeVarLen64BigEndian(data[4:7])) + } + + if sec == 0 { + return formatZeroTime(int(usec), int(dec)), n + } + + return FracTime{time.Unix(sec, usec*1000), int(dec)}.String(), n +} + +// DecodeDatetime decodes DATETIME value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/datetime.html +func DecodeDatetime(v uint64) string { + d := v / 1000000 + t := v % 1000000 + return FracTime{Time: time.Date(int(d/10000), + time.Month((d%10000)/100), + int(d%100), + int(t/10000), + int((t%10000)/100), + int(t%100), + 0, + Timezone, + )}.String() +} + +// DecodeDatetime2 decodes DATETIME v2 value. +// Spec: https://dev.mysql.com/doc/refman/8.0/en/datetime.html +// Implementation borrowed from https://github.com/siddontang/go-mysql/ +func DecodeDatetime2(data []byte, dec uint16) (string, int) { + const offset int64 = 0x8000000000 + // get datetime binary length + n := int(5 + (dec+1)/2) + + intPart := int64(DecodeVarLen64BigEndian(data[0:5])) - offset + var frac int64 + + switch dec { + case 1, 2: + frac = int64(data[5]) * 10000 + case 3, 4: + frac = int64(binary.BigEndian.Uint16(data[5:7])) * 100 + case 5, 6: + frac = int64(DecodeVarLen64BigEndian(data[5:8])) + } + + if intPart == 0 { + return formatZeroTime(int(frac), int(dec)), n + } + + tmp := intPart<<24 + frac + // handle sign??? + if tmp < 0 { + tmp = -tmp + } + + // var secPart int64 = tmp % (1 << 24) + ymdhms := tmp >> 24 + + ymd := ymdhms >> 17 + ym := ymd >> 5 + hms := ymdhms % (1 << 17) + + day := int(ymd % (1 << 5)) + month := int(ym % 13) + year := int(ym / 13) + + second := int(hms % (1 << 6)) + minute := int((hms >> 6) % (1 << 6)) + hour := int((hms >> 12)) + + return FracTime{time.Date(year, time.Month(month), day, hour, minute, second, int(frac*1000), Timezone), int(dec)}.String(), n +} + var ( fracTimeFormat = [...]string{ "2006-01-02T15:04:05Z",