1
0
Fork 0
bocadillo/binlog/event_rows.go

284 lines
7.1 KiB
Go

package binlog
import (
"fmt"
"time"
"github.com/localhots/bocadillo/mysql"
"github.com/localhots/bocadillo/tools"
)
// RowsEvent contains a Rows Event.
type RowsEvent struct {
Type EventType
TableID uint64
Flags uint16
ExtraData []byte
ColumnCount uint64
ColumnBitmap1 []byte
ColumnBitmap2 []byte
Rows [][]interface{}
}
type rowsFlag uint16
const (
rowsFlagEndOfStatement rowsFlag = 0x0001
rowsFlagNoForeignKeyChecks rowsFlag = 0x0002
rowsFlagNoUniqueKeyChecks rowsFlag = 0x0004
rowsFlagRowHasColumns rowsFlag = 0x0008
freeTableMapID = 0x00FFFFFF
)
// PeekTableID returns table ID without decoding whole event.
func (e *RowsEvent) PeekTableID(connBuff []byte, fd FormatDescription) uint64 {
if fd.TableIDSize(e.Type) == 6 {
return mysql.DecodeUint48(connBuff)
}
return uint64(mysql.DecodeUint32(connBuff))
}
// Decode decodes given buffer into a rows event event.
func (e *RowsEvent) Decode(connBuff []byte, fd FormatDescription, td TableDescription) error {
// pretty.Println(data)
buf := tools.NewBuffer(connBuff)
idSize := fd.TableIDSize(e.Type)
if idSize == 6 {
e.TableID = buf.ReadUint48()
} else {
e.TableID = uint64(buf.ReadUint32())
}
e.Flags = buf.ReadUint16()
if RowsEventHasExtraData(e.Type) {
// Extra data length is part of extra data, deduct 2 bytes as they
// already store its length
extraLen := buf.ReadUint16() - 2
e.ExtraData = buf.ReadStringVarLen(int(extraLen))
}
e.ColumnCount, _, _ = buf.ReadUintLenEnc()
e.ColumnBitmap1 = buf.ReadStringVarLen(int(e.ColumnCount+7) / 8)
if RowsEventHasSecondBitmap(e.Type) {
e.ColumnBitmap2 = buf.ReadStringVarLen(int(e.ColumnCount+7) / 8)
}
// pretty.Println(e.Type.String(), buf.Cur())
e.Rows = make([][]interface{}, 0)
for buf.More() {
row, err := e.decodeRows(buf, td, e.ColumnBitmap1)
if err != nil {
return err
}
e.Rows = append(e.Rows, row)
if RowsEventHasSecondBitmap(e.Type) {
row, err := e.decodeRows(buf, td, e.ColumnBitmap2)
if err != nil {
return err
}
e.Rows = append(e.Rows, row)
}
}
return nil
}
func (e *RowsEvent) decodeRows(buf *tools.Buffer, td TableDescription, bm []byte) ([]interface{}, error) {
count := 0
for i := 0; i < int(e.ColumnCount); i++ {
if isBitSet(bm, i) {
count++
}
}
count = (count + 7) / 8
nullBM := buf.ReadStringVarLen(count)
nullIdx := 0
row := make([]interface{}, e.ColumnCount)
for i := 0; i < int(e.ColumnCount); i++ {
if !isBitSet(bm, i) {
continue
}
isNull := (uint32(nullBM[nullIdx/8]) >> uint32(nullIdx%8)) & 1
nullIdx++
if isNull > 0 {
row[i] = nil
continue
}
row[i] = e.decodeValue(buf, mysql.ColumnType(td.ColumnTypes[i]), td.ColumnMeta[i])
// fmt.Printf("Parsed %s, meta %x, value %++v\n",
// mysql.ColumnType(td.ColumnTypes[i]).String(), td.ColumnMeta[i], row[i],
// )
}
return row, nil
}
func (e *RowsEvent) decodeValue(buf *tools.Buffer, ct mysql.ColumnType, meta uint16) interface{} {
var length int
if ct == mysql.ColumnTypeString {
if meta > 0xFF {
typeByte := uint8(meta >> 8)
lengthByte := uint8(meta & 0xFF)
if typeByte&0x30 != 0x30 {
ct = mysql.ColumnType(typeByte | 0x30)
length = int(uint16(lengthByte) | (uint16((typeByte&0x30)^0x30) << 4))
} else {
ct = mysql.ColumnType(typeByte)
length = int(lengthByte)
}
} else {
length = int(meta)
}
}
switch ct {
case mysql.ColumnTypeNull:
return nil
// Integer
case mysql.ColumnTypeTiny:
return buf.ReadUint8()
case mysql.ColumnTypeShort:
return buf.ReadUint16()
case mysql.ColumnTypeInt24:
return buf.ReadUint24()
case mysql.ColumnTypeLong:
return buf.ReadUint32()
case mysql.ColumnTypeLonglong:
return buf.ReadUint64()
// Float
case mysql.ColumnTypeFloat:
return buf.ReadFloat32()
case mysql.ColumnTypeDouble:
return buf.ReadFloat64()
// Decimals
case mysql.ColumnTypeNewDecimal:
precision := int(meta >> 8)
decimals := int(meta & 0xFF)
dec, n := mysql.DecodeDecimal(buf.Cur(), precision, decimals)
buf.Skip(n)
return dec
// Date and Time
case mysql.ColumnTypeYear:
return mysql.DecodeYear(buf.ReadUint8())
case mysql.ColumnTypeDate:
return mysql.DecodeDate(buf.ReadUint24())
case mysql.ColumnTypeTime:
return mysql.DecodeTime(buf.ReadUint24())
case mysql.ColumnTypeTime2:
v, n := mysql.DecodeTime2(buf.Cur(), meta)
buf.Skip(n)
return v
case mysql.ColumnTypeTimestamp:
ts := buf.ReadUint32()
return mysql.FracTime{Time: time.Unix(int64(ts), 0)}.String()
case mysql.ColumnTypeTimestamp2:
v, n := mysql.DecodeTimestamp2(buf.Cur(), meta)
buf.Skip(n)
return v
case mysql.ColumnTypeDatetime:
return mysql.DecodeDatetime(buf.ReadUint64())
case mysql.ColumnTypeDatetime2:
v, n := mysql.DecodeDatetime2(buf.Cur(), meta)
buf.Skip(n)
return v
// Strings
case mysql.ColumnTypeString:
return readString(buf, length)
case mysql.ColumnTypeVarchar, mysql.ColumnTypeVarstring:
return readString(buf, int(meta))
// Blobs
case mysql.ColumnTypeBlob, mysql.ColumnTypeGeometry:
return buf.ReadStringVarEnc(int(meta))
case mysql.ColumnTypeJSON:
jdata := buf.ReadStringVarEnc(int(meta))
rawj, _ := mysql.DecodeJSON(jdata)
return rawj
case mysql.ColumnTypeTinyblob:
return buf.ReadStringVarEnc(1)
case mysql.ColumnTypeMediumblob:
return buf.ReadStringVarEnc(3)
case mysql.ColumnTypeLongblob:
return buf.ReadStringVarEnc(4)
// Bits
case mysql.ColumnTypeBit:
nbits := int(((meta >> 8) * 8) + (meta & 0xFF))
length = int(nbits+7) / 8
return mysql.DecodeBit(buf.Cur(), nbits, length)
case mysql.ColumnTypeSet:
length = int(meta & 0xFF)
nbits := length * 8
return mysql.DecodeBit(buf.Cur(), nbits, length)
// Stuff
case mysql.ColumnTypeEnum:
return buf.ReadVarLen64(int(meta & 0xFF))
// Unsupported
case mysql.ColumnTypeDecimal:
// Old decimal
fallthrough
case mysql.ColumnTypeNewDate:
// Too new
fallthrough
default:
return fmt.Errorf("unsupported type: %d (%s) %x %x", ct, ct.String(), meta, buf.Cur())
}
}
// FIXME: Something is fishy with this whole string decoding. It seems like it
// could be simplified greatly
func readString(buf *tools.Buffer, length int) string {
if length < 256 {
return string(buf.ReadStringVarEnc(1))
}
return string(buf.ReadStringVarEnc(2))
}
func isBitSet(bm []byte, i int) bool {
return bm[i>>3]&(1<<(uint(i)&7)) > 0
}
// RowsEventVersion returns rows event versions. If event is not a rows type -1
// is returned.
func RowsEventVersion(et EventType) int {
switch et {
case EventTypeWriteRowsV0, EventTypeUpdateRowsV0, EventTypeDeleteRowsV0:
return 0
case EventTypeWriteRowsV1, EventTypeUpdateRowsV1, EventTypeDeleteRowsV1:
return 1
case EventTypeWriteRowsV2, EventTypeUpdateRowsV2, EventTypeDeleteRowsV2:
return 2
default:
return -1
}
}
// RowsEventHasExtraData returns true if given event is of rows type and
// contains extra data.
func RowsEventHasExtraData(et EventType) bool {
return RowsEventVersion(et) == 2
}
// RowsEventHasSecondBitmap returns true if given event is of rows type and
// contains a second bitmap.
func RowsEventHasSecondBitmap(et EventType) bool {
switch et {
case EventTypeUpdateRowsV1, EventTypeUpdateRowsV2:
return true
default:
return false
}
}