зеркало из https://github.com/github/vitess-gh.git
283 строки
8.8 KiB
Go
283 строки
8.8 KiB
Go
// Copyright 2014, Google Inc. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mysqlctl
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/youtube/vitess/go/vt/mysqlctl/replication"
|
|
binlogdatapb "github.com/youtube/vitess/go/vt/proto/binlogdata"
|
|
)
|
|
|
|
// binlogEvent wraps a raw packet buffer and provides methods to examine it
|
|
// by partially implementing replication.BinlogEvent. These methods can be composed
|
|
// into flavor-specific event types to pull in common parsing code.
|
|
//
|
|
// The default v4 header format is:
|
|
// offset : size
|
|
// +============================+
|
|
// | timestamp 0 : 4 |
|
|
// +----------------------------+
|
|
// | type_code 4 : 1 |
|
|
// +----------------------------+
|
|
// | server_id 5 : 4 |
|
|
// +----------------------------+
|
|
// | event_length 9 : 4 |
|
|
// +----------------------------+
|
|
// | next_position 13 : 4 |
|
|
// +----------------------------+
|
|
// | flags 17 : 2 |
|
|
// +----------------------------+
|
|
// | extra_headers 19 : x-19 |
|
|
// +============================+
|
|
// http://dev.mysql.com/doc/internals/en/event-header-fields.html
|
|
type binlogEvent []byte
|
|
|
|
// IsValid implements BinlogEvent.IsValid().
|
|
func (ev binlogEvent) IsValid() bool {
|
|
bufLen := len(ev.Bytes())
|
|
|
|
// The buffer must be at least 19 bytes to contain a valid header.
|
|
if bufLen < 19 {
|
|
return false
|
|
}
|
|
|
|
// It's now safe to use methods that examine header fields.
|
|
// Let's see if the event is right about its own size.
|
|
evLen := ev.Length()
|
|
if evLen < 19 || evLen != uint32(bufLen) {
|
|
return false
|
|
}
|
|
|
|
// Everything's there, so we shouldn't have any out-of-bounds issues while
|
|
// reading header fields or constant-offset data fields. We should still check
|
|
// bounds any time we compute an offset based on values in the buffer itself.
|
|
return true
|
|
}
|
|
|
|
// Bytes returns the underlying byte buffer.
|
|
func (ev binlogEvent) Bytes() []byte {
|
|
return []byte(ev)
|
|
}
|
|
|
|
// Type returns the type_code field from the header.
|
|
func (ev binlogEvent) Type() byte {
|
|
return ev.Bytes()[4]
|
|
}
|
|
|
|
// Flags returns the flags field from the header.
|
|
func (ev binlogEvent) Flags() uint16 {
|
|
return binary.LittleEndian.Uint16(ev.Bytes()[17 : 17+2])
|
|
}
|
|
|
|
// Timestamp returns the timestamp field from the header.
|
|
func (ev binlogEvent) Timestamp() uint32 {
|
|
return binary.LittleEndian.Uint32(ev.Bytes()[:4])
|
|
}
|
|
|
|
// ServerID returns the server_id field from the header.
|
|
func (ev binlogEvent) ServerID() uint32 {
|
|
return binary.LittleEndian.Uint32(ev.Bytes()[5 : 5+4])
|
|
}
|
|
|
|
// Length returns the event_length field from the header.
|
|
func (ev binlogEvent) Length() uint32 {
|
|
return binary.LittleEndian.Uint32(ev.Bytes()[9 : 9+4])
|
|
}
|
|
|
|
// IsFormatDescription implements BinlogEvent.IsFormatDescription().
|
|
func (ev binlogEvent) IsFormatDescription() bool {
|
|
return ev.Type() == 15
|
|
}
|
|
|
|
// IsQuery implements BinlogEvent.IsQuery().
|
|
func (ev binlogEvent) IsQuery() bool {
|
|
return ev.Type() == 2
|
|
}
|
|
|
|
// IsRotate implements BinlogEvent.IsRotate().
|
|
func (ev binlogEvent) IsRotate() bool {
|
|
return ev.Type() == 4
|
|
}
|
|
|
|
// IsXID implements BinlogEvent.IsXID().
|
|
func (ev binlogEvent) IsXID() bool {
|
|
return ev.Type() == 16
|
|
}
|
|
|
|
// IsIntVar implements BinlogEvent.IsIntVar().
|
|
func (ev binlogEvent) IsIntVar() bool {
|
|
return ev.Type() == 5
|
|
}
|
|
|
|
// IsRand implements BinlogEvent.IsRand().
|
|
func (ev binlogEvent) IsRand() bool {
|
|
return ev.Type() == 13
|
|
}
|
|
|
|
// Format implements BinlogEvent.Format().
|
|
//
|
|
// Expected format (L = total length of event data):
|
|
// # bytes field
|
|
// 2 format version
|
|
// 50 server version string, 0-padded but not necessarily 0-terminated
|
|
// 4 timestamp (same as timestamp header field)
|
|
// 1 header length
|
|
func (ev binlogEvent) Format() (f replication.BinlogFormat, err error) {
|
|
// FORMAT_DESCRIPTION_EVENT has a fixed header size of 19 because we have to
|
|
// read it before we know the header_length.
|
|
data := ev.Bytes()[19:]
|
|
|
|
f.FormatVersion = binary.LittleEndian.Uint16(data[:2])
|
|
if f.FormatVersion != 4 {
|
|
return f, fmt.Errorf("format version = %d, we only support version 4", f.FormatVersion)
|
|
}
|
|
f.ServerVersion = string(bytes.TrimRight(data[2:2+50], "\x00"))
|
|
f.HeaderLength = data[2+50+4]
|
|
if f.HeaderLength < 19 {
|
|
return f, fmt.Errorf("header length = %d, should be >= 19", f.HeaderLength)
|
|
}
|
|
|
|
// MySQL/MariaDB 5.3+ always adds a 4-byte checksum to the end of a
|
|
// FORMAT_DESCRIPTION_EVENT, regardless of the server setting. The byte
|
|
// immediately before that checksum tells us which checksum algorithm (if any)
|
|
// is used for the rest of the events.
|
|
f.ChecksumAlgorithm = data[len(data)-5]
|
|
return f, nil
|
|
}
|
|
|
|
// Query implements BinlogEvent.Query().
|
|
//
|
|
// Expected format (L = total length of event data):
|
|
// # bytes field
|
|
// 4 thread_id
|
|
// 4 execution time
|
|
// 1 length of db_name, not including NULL terminator (X)
|
|
// 2 error code
|
|
// 2 length of status vars block (Y)
|
|
// Y status vars block
|
|
// X+1 db_name + NULL terminator
|
|
// L-X-1-Y SQL statement (no NULL terminator)
|
|
func (ev binlogEvent) Query(f replication.BinlogFormat) (query replication.Query, err error) {
|
|
const varsPos = 4 + 4 + 1 + 2 + 2
|
|
|
|
data := ev.Bytes()[f.HeaderLength:]
|
|
|
|
// length of database name
|
|
dbLen := int(data[4+4])
|
|
// length of status variables block
|
|
varsLen := int(binary.LittleEndian.Uint16(data[4+4+1+2 : 4+4+1+2+2]))
|
|
|
|
// position of database name
|
|
dbPos := varsPos + varsLen
|
|
// position of SQL query
|
|
sqlPos := dbPos + dbLen + 1 // +1 for NULL terminator
|
|
if sqlPos > len(data) {
|
|
return query, fmt.Errorf("SQL query position overflows buffer (%v > %v)", sqlPos, len(data))
|
|
}
|
|
|
|
// We've checked that the buffer is big enough for sql, so everything before
|
|
// it (db and vars) is in-bounds too.
|
|
query.Database = string(data[dbPos : dbPos+dbLen])
|
|
query.SQL = string(data[sqlPos:])
|
|
|
|
// Scan the status vars for ones we care about. This requires us to know the
|
|
// size of every var that comes before the ones we're interested in.
|
|
vars := data[varsPos : varsPos+varsLen]
|
|
|
|
varsLoop:
|
|
for pos := 0; pos < len(vars); {
|
|
code := vars[pos]
|
|
pos++
|
|
|
|
// All codes are optional, but if present they must occur in numerically
|
|
// increasing order (except for 6 which occurs in the place of 2) to allow
|
|
// for backward compatibility.
|
|
switch code {
|
|
case 0, 3: // Q_FLAGS2_CODE, Q_AUTO_INCREMENT
|
|
pos += 4
|
|
case 1: // Q_SQL_MODE_CODE
|
|
pos += 8
|
|
case 2: // Q_CATALOG_CODE (used in MySQL 5.0.0 - 5.0.3)
|
|
if pos+1 > len(vars) {
|
|
return query, fmt.Errorf("Q_CATALOG_CODE status var overflows buffer (%v + 1 > %v)", pos, len(vars))
|
|
}
|
|
pos += 1 + int(vars[pos]) + 1
|
|
case 6: // Q_CATALOG_NZ_CODE (used in MySQL > 5.0.3 to replace 2)
|
|
if pos+1 > len(vars) {
|
|
return query, fmt.Errorf("Q_CATALOG_NZ_CODE status var overflows buffer (%v + 1 > %v)", pos, len(vars))
|
|
}
|
|
pos += 1 + int(vars[pos])
|
|
case 4: // Q_CHARSET_CODE
|
|
if pos+6 > len(vars) {
|
|
return query, fmt.Errorf("Q_CHARSET_CODE status var overflows buffer (%v + 6 > %v)", pos, len(vars))
|
|
}
|
|
query.Charset = &binlogdatapb.Charset{
|
|
Client: int32(binary.LittleEndian.Uint16(vars[pos : pos+2])),
|
|
Conn: int32(binary.LittleEndian.Uint16(vars[pos+2 : pos+4])),
|
|
Server: int32(binary.LittleEndian.Uint16(vars[pos+4 : pos+6])),
|
|
}
|
|
pos += 6
|
|
default:
|
|
// If we see something higher than what we're interested in, we can stop.
|
|
break varsLoop
|
|
}
|
|
}
|
|
|
|
return query, nil
|
|
}
|
|
|
|
// IntVar implements BinlogEvent.IntVar().
|
|
//
|
|
// Expected format (L = total length of event data):
|
|
// # bytes field
|
|
// 1 variable ID
|
|
// 8 variable value
|
|
func (ev binlogEvent) IntVar(f replication.BinlogFormat) (name string, value uint64, err error) {
|
|
data := ev.Bytes()[f.HeaderLength:]
|
|
|
|
switch data[0] {
|
|
case 1:
|
|
name = "LAST_INSERT_ID"
|
|
case 2:
|
|
name = "INSERT_ID"
|
|
default:
|
|
return "", 0, fmt.Errorf("invalid IntVar ID: %v", data[0])
|
|
}
|
|
|
|
value = binary.LittleEndian.Uint64(data[1 : 1+8])
|
|
return name, value, nil
|
|
}
|
|
|
|
// Rand implements BinlogEvent.Rand().
|
|
//
|
|
// Expected format (L = total length of event data):
|
|
// # bytes field
|
|
// 8 seed 1
|
|
// 8 seed 2
|
|
func (ev binlogEvent) Rand(f replication.BinlogFormat) (seed1 uint64, seed2 uint64, err error) {
|
|
data := ev.Bytes()[f.HeaderLength:]
|
|
seed1 = binary.LittleEndian.Uint64(data[0:8])
|
|
seed2 = binary.LittleEndian.Uint64(data[8 : 8+8])
|
|
return seed1, seed2, nil
|
|
}
|
|
|
|
// IsBeginGTID implements BinlogEvent.IsBeginGTID().
|
|
func (ev binlogEvent) IsBeginGTID(f replication.BinlogFormat) bool {
|
|
return false
|
|
}
|
|
|
|
// These constants are common between MariaDB 10.0 and MySQL 5.6.
|
|
const (
|
|
// BinlogChecksumAlgOff indicates that checksums are supported but off.
|
|
BinlogChecksumAlgOff = 0
|
|
// BinlogChecksumAlgCRC32 indicates that CRC32 checksums are used.
|
|
BinlogChecksumAlgCRC32 = 1
|
|
// BinlogChecksumAlgUndef indicates that checksums are not supported.
|
|
BinlogChecksumAlgUndef = 255
|
|
)
|