New enhanced reader with column names and integer signing
This commit is contained in:
parent
b0e2e79727
commit
7e08b8e830
|
@ -0,0 +1,174 @@
|
|||
package reader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"github.com/localhots/bocadillo/binlog"
|
||||
"github.com/localhots/bocadillo/mysql"
|
||||
"github.com/localhots/bocadillo/reader/schema"
|
||||
"github.com/localhots/bocadillo/reader/slave"
|
||||
_ "github.com/localhots/mysql" // MySQL driver
|
||||
)
|
||||
|
||||
// EnhancedReader is an extended version of the reader that maintains schema
|
||||
// details to add column names and signed integers support.
|
||||
type EnhancedReader struct {
|
||||
reader *Reader
|
||||
schemaMgr *schema.Manager
|
||||
}
|
||||
|
||||
// EnhancedRowsEvent ...
|
||||
type EnhancedRowsEvent struct {
|
||||
Type binlog.EventType
|
||||
Table binlog.TableDescription
|
||||
Rows []map[string]interface{}
|
||||
}
|
||||
|
||||
// NewEnhanced creates a new enhanced binary log reader.
|
||||
func NewEnhanced(dsn string, sc slave.Config) (*EnhancedReader, error) {
|
||||
r, err := New(dsn, sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &EnhancedReader{
|
||||
reader: r,
|
||||
schemaMgr: schema.NewManager(conn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WhitelistTables adds given tables of the given database to processing white
|
||||
// list.
|
||||
func (r *EnhancedReader) WhitelistTables(database string, tables ...string) error {
|
||||
for _, tbl := range tables {
|
||||
if err := r.schemaMgr.Manage(database, tbl); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadEvent reads next event from the binary log.
|
||||
func (r *EnhancedReader) ReadEvent() (*Event, error) {
|
||||
evt, err := r.reader.ReadEvent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch evt.Header.Type {
|
||||
case binlog.EventTypeQuery:
|
||||
var qe binlog.QueryEvent
|
||||
qe.Decode(evt.Buffer)
|
||||
err = r.schemaMgr.ProcessQuery(string(qe.Schema), string(qe.Query))
|
||||
}
|
||||
|
||||
return evt, err
|
||||
}
|
||||
|
||||
// NextRowsEvent returns the next rows event for a whitelisted table. It blocks
|
||||
// until next event is received or context is cancelled.
|
||||
func (r *EnhancedReader) NextRowsEvent(ctx context.Context) (*EnhancedRowsEvent, error) {
|
||||
evtch := make(chan *EnhancedRowsEvent)
|
||||
errch := make(chan error)
|
||||
go func() {
|
||||
evt, err := r.nextRowsEvent()
|
||||
if err != nil {
|
||||
errch <- err
|
||||
} else {
|
||||
evtch <- evt
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case evt := <-evtch:
|
||||
return evt, nil
|
||||
case err := <-errch:
|
||||
return nil, err
|
||||
case <-ctx.Done():
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *EnhancedReader) nextRowsEvent() (*EnhancedRowsEvent, error) {
|
||||
for {
|
||||
evt, err := r.reader.ReadEvent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if it's a rows event
|
||||
if binlog.RowsEventVersion(evt.Header.Type) < 0 {
|
||||
// fmt.Println("Not a rows event", evt.Header.Type.String())
|
||||
continue
|
||||
}
|
||||
|
||||
tbl := r.schemaMgr.Schema.Table(evt.Table.SchemaName, evt.Table.TableName)
|
||||
if tbl == nil {
|
||||
// Not whitelisted
|
||||
continue
|
||||
}
|
||||
|
||||
re, err := evt.DecodeRows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ere := EnhancedRowsEvent{
|
||||
Type: re.Type,
|
||||
Table: *evt.Table,
|
||||
Rows: make([]map[string]interface{}, len(re.Rows)),
|
||||
}
|
||||
for i, row := range re.Rows {
|
||||
erow := make(map[string]interface{}, len(row))
|
||||
for j, val := range row {
|
||||
col := tbl.Column(j)
|
||||
if col == nil {
|
||||
return nil, errors.New("column index undefined")
|
||||
}
|
||||
ct := mysql.ColumnType(evt.Table.ColumnTypes[j])
|
||||
if !col.Unsigned {
|
||||
val = signNumber(val, ct)
|
||||
}
|
||||
erow[col.Name] = val
|
||||
}
|
||||
ere.Rows[i] = erow
|
||||
}
|
||||
|
||||
return &ere, nil
|
||||
}
|
||||
}
|
||||
|
||||
// State returns current position in the binary log.
|
||||
func (r *EnhancedReader) State() binlog.Position {
|
||||
return r.reader.state
|
||||
}
|
||||
|
||||
// Close underlying database connection.
|
||||
func (r *EnhancedReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
func signNumber(val interface{}, ct mysql.ColumnType) interface{} {
|
||||
switch tval := val.(type) {
|
||||
case uint8:
|
||||
return mysql.SignUint8(tval)
|
||||
case uint16:
|
||||
return mysql.SignUint16(tval)
|
||||
case uint32:
|
||||
if ct == mysql.ColumnTypeInt24 {
|
||||
return mysql.SignUint24(tval)
|
||||
}
|
||||
return mysql.SignUint32(tval)
|
||||
case uint64:
|
||||
return mysql.SignUint64(tval)
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package reader
|
|||
import (
|
||||
"github.com/juju/errors"
|
||||
"github.com/localhots/bocadillo/binlog"
|
||||
"github.com/localhots/bocadillo/reader/schema"
|
||||
"github.com/localhots/bocadillo/reader/slave"
|
||||
)
|
||||
|
||||
|
@ -13,7 +12,6 @@ type Reader struct {
|
|||
state binlog.Position
|
||||
format binlog.FormatDescription
|
||||
tableMap map[uint64]binlog.TableDescription
|
||||
schema *schema.Schema
|
||||
}
|
||||
|
||||
// Event contains binlog event details.
|
||||
|
|
Loading…
Reference in New Issue