Finish rows parsing
This commit is contained in:
		
							parent
							
								
									42f22dc377
								
							
						
					
					
						commit
						4d80368814
					
				@ -1,6 +1,9 @@
 | 
			
		||||
package binlog
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/localhots/bocadillo/mysql"
 | 
			
		||||
	"github.com/localhots/bocadillo/tools"
 | 
			
		||||
	"github.com/localhots/pretty"
 | 
			
		||||
@ -63,13 +66,28 @@ func (e *RowsEvent) Decode(connBuff []byte, fd FormatDescription, td TableDescri
 | 
			
		||||
		e.ColumnBitmap2 = buf.ReadStringVarLen(int(e.ColumnCount+7) / 8)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pretty.Println(e.Type.String(), e, td, buf.Cur())
 | 
			
		||||
	// pretty.Println(e.Type.String(), buf.Cur())
 | 
			
		||||
 | 
			
		||||
	e.decodeRows(buf, td, e.ColumnBitmap1)
 | 
			
		||||
	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) {
 | 
			
		||||
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) {
 | 
			
		||||
@ -81,8 +99,6 @@ func (e *RowsEvent) decodeRows(buf *tools.Buffer, td TableDescription, bm []byte
 | 
			
		||||
	nullBM := buf.ReadStringVarLen(count)
 | 
			
		||||
	nullIdx := 0
 | 
			
		||||
	row := make([]interface{}, e.ColumnCount)
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	for i := 0; i < int(e.ColumnCount); i++ {
 | 
			
		||||
		if !isBitSet(bm, i) {
 | 
			
		||||
			continue
 | 
			
		||||
@ -95,73 +111,159 @@ func (e *RowsEvent) decodeRows(buf *tools.Buffer, td TableDescription, bm []byte
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		row[i], err = e.decodeValue(buf, mysql.ColumnType(td.ColumnTypes[i]), td.ColumnMeta[i])
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		row[i] = e.decodeValue(buf, mysql.ColumnType(td.ColumnTypes[i]), td.ColumnMeta[i])
 | 
			
		||||
		// pretty.Println("PARSED", 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{}, error) {
 | 
			
		||||
	switch ct {
 | 
			
		||||
	case mysql.ColumnTypeDecimal:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeTiny:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeShort:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeLong:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeFloat:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeDouble:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeNull:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeTimestamp:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeLonglong:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeInt24:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeDate:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeTime:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeDatetime:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeYear:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeVarchar:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeBit:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
 | 
			
		||||
	case mysql.ColumnTypeJSON:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeNewDecimal:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeEnum:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeSet:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeTinyblob:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeMediumblob:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeLongblob:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeBlob:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeVarstring:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeString:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
	case mysql.ColumnTypeGeometry:
 | 
			
		||||
		pretty.Println("Type", ct.String())
 | 
			
		||||
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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, nil
 | 
			
		||||
 | 
			
		||||
	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 uint16(buf.ReadUint8()) + 1900
 | 
			
		||||
	case mysql.ColumnTypeDate:
 | 
			
		||||
		v := buf.ReadUint24()
 | 
			
		||||
		if v == 0 {
 | 
			
		||||
			return "0000-00-00"
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%04d-%02d-%02d", v/(16*32), v/32%16, v%32)
 | 
			
		||||
	case mysql.ColumnTypeTime:
 | 
			
		||||
		v := buf.ReadUint24()
 | 
			
		||||
		if v == 0 {
 | 
			
		||||
			return "00:00:00"
 | 
			
		||||
		}
 | 
			
		||||
		var sign string
 | 
			
		||||
		if v < 0 {
 | 
			
		||||
			sign = "-"
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s%02d:%02d:%02d", sign, v/10000, (v%10000)/100, v%100)
 | 
			
		||||
	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:
 | 
			
		||||
		v := buf.ReadUint64()
 | 
			
		||||
		d := v / 1000000
 | 
			
		||||
		t := v % 1000000
 | 
			
		||||
		return mysql.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,
 | 
			
		||||
			time.UTC)}.String()
 | 
			
		||||
	case mysql.ColumnTypeDatetime2:
 | 
			
		||||
		v, n := mysql.DecodeDatetime2(buf.Cur(), meta)
 | 
			
		||||
		buf.Skip(n)
 | 
			
		||||
		return v
 | 
			
		||||
 | 
			
		||||
	// Strings
 | 
			
		||||
	// FIXME
 | 
			
		||||
	case mysql.ColumnTypeString:
 | 
			
		||||
		return readString(buf, length)
 | 
			
		||||
	case mysql.ColumnTypeVarchar, mysql.ColumnTypeVarstring:
 | 
			
		||||
		return readString(buf, int(meta))
 | 
			
		||||
 | 
			
		||||
	// Blobs
 | 
			
		||||
	case mysql.ColumnTypeBlob, mysql.ColumnTypeGeometry, mysql.ColumnTypeJSON:
 | 
			
		||||
		return buf.ReadStringVarEnc(int(meta))
 | 
			
		||||
	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:
 | 
			
		||||
		pretty.Printf("UNSUPPORTED Type %d %s %x %x\n", ct, ct.String(), meta, buf.Cur())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"github.com/juju/errors"
 | 
			
		||||
	"github.com/localhots/bocadillo/binlog"
 | 
			
		||||
	"github.com/localhots/bocadillo/schema"
 | 
			
		||||
	"github.com/localhots/pretty"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Reader ...
 | 
			
		||||
@ -20,6 +19,8 @@ type Reader struct {
 | 
			
		||||
type Event struct {
 | 
			
		||||
	Header binlog.EventHeader
 | 
			
		||||
	Body   []byte
 | 
			
		||||
	Table  *binlog.TableDescription
 | 
			
		||||
	Rows   *binlog.RowsEvent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewReader ...
 | 
			
		||||
@ -73,13 +74,13 @@ func (r *Reader) ReadEvent() (*Event, error) {
 | 
			
		||||
		if err = fde.Decode(evt.Body); err == nil {
 | 
			
		||||
			r.format = fde.FormatDescription
 | 
			
		||||
		}
 | 
			
		||||
		pretty.Println(evt.Header.Type.String(), r.format)
 | 
			
		||||
		// pretty.Println(evt.Header.Type.String(), r.format)
 | 
			
		||||
	case binlog.EventTypeRotate:
 | 
			
		||||
		var re binlog.RotateEvent
 | 
			
		||||
		if err = re.Decode(evt.Body, r.format); err == nil {
 | 
			
		||||
			r.state = re.NextFile
 | 
			
		||||
		}
 | 
			
		||||
		pretty.Println(evt.Header.Type.String(), r.state)
 | 
			
		||||
		// pretty.Println(evt.Header.Type.String(), r.state)
 | 
			
		||||
	case binlog.EventTypeTableMap:
 | 
			
		||||
		var tme binlog.TableMapEvent
 | 
			
		||||
		if err = tme.Decode(evt.Body, r.format); err == nil {
 | 
			
		||||
@ -102,9 +103,11 @@ func (r *Reader) ReadEvent() (*Event, error) {
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, errors.New("Unknown table ID")
 | 
			
		||||
		}
 | 
			
		||||
		if err = re.Decode(evt.Body, r.format, td); err == nil {
 | 
			
		||||
			pretty.Println(re)
 | 
			
		||||
		if err = re.Decode(evt.Body, r.format, td); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		evt.Table = &td
 | 
			
		||||
		evt.Rows = &re
 | 
			
		||||
	case binlog.EventTypeXID:
 | 
			
		||||
		// TODO: Add support
 | 
			
		||||
	case binlog.EventTypeGTID:
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ package reader
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql/driver"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
@ -101,7 +100,6 @@ func (c *SlaveConn) RegisterSlave() error {
 | 
			
		||||
	// buf.WriteUint32(replicationRank)
 | 
			
		||||
	// buf.WriteUint32(masterID)
 | 
			
		||||
 | 
			
		||||
	fmt.Println(hex.Dump(buf.Bytes()))
 | 
			
		||||
	return c.runCmd(buf.Bytes())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package tools
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"math"
 | 
			
		||||
 | 
			
		||||
	"github.com/localhots/bocadillo/mysql"
 | 
			
		||||
)
 | 
			
		||||
@ -42,6 +43,11 @@ func (b *Buffer) Cur() []byte {
 | 
			
		||||
	return b.data[b.pos:]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// More returns true if there's more to read.
 | 
			
		||||
func (b *Buffer) More() bool {
 | 
			
		||||
	return b.pos < len(b.data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Bytes returns entire buffer contents.
 | 
			
		||||
func (b *Buffer) Bytes() []byte {
 | 
			
		||||
	return b.data
 | 
			
		||||
@ -84,6 +90,22 @@ func (b *Buffer) ReadUintLenEnc() (val uint64, isNull bool, size int) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadVarLen64 reads a number encoded in given size of bytes and advances
 | 
			
		||||
// cursor accordingly.
 | 
			
		||||
func (b *Buffer) ReadVarLen64(n int) uint64 {
 | 
			
		||||
	return mysql.DecodeVarLen64(b.Read(n), n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadFloat32 reads a float32 and advances cursor by 4 bytes.
 | 
			
		||||
func (b *Buffer) ReadFloat32() float32 {
 | 
			
		||||
	return math.Float32frombits(b.ReadUint32())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadFloat64 reads a float64 and advances cursor by 8 bytes.
 | 
			
		||||
func (b *Buffer) ReadFloat64() float64 {
 | 
			
		||||
	return math.Float64frombits(b.ReadUint64())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadStringNullTerm reads a NULL-terminated string and advances cursor by its
 | 
			
		||||
// length plus 1 extra byte.
 | 
			
		||||
func (b *Buffer) ReadStringNullTerm() []byte {
 | 
			
		||||
@ -98,6 +120,13 @@ func (b *Buffer) ReadStringVarLen(n int) []byte {
 | 
			
		||||
	return mysql.DecodeStringVarLen(b.Read(n), n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadStringVarEnc reads a variable-length length of the string and the string
 | 
			
		||||
// itself, then advances cursor by the same number of bytes.
 | 
			
		||||
func (b *Buffer) ReadStringVarEnc(n int) []byte {
 | 
			
		||||
	length := int(mysql.DecodeVarLen64(b.Read(n), n))
 | 
			
		||||
	return mysql.DecodeStringVarLen(b.Read(length), length)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadStringLenEnc reads a length-encoded string and advances cursor
 | 
			
		||||
// accordingly.
 | 
			
		||||
func (b *Buffer) ReadStringLenEnc() (str []byte, size int) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user