138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package binlog
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/localhots/bocadillo/tools"
|
|
)
|
|
|
|
// FormatDescription is a description of binary log format.
|
|
type FormatDescription struct {
|
|
Version uint16
|
|
ServerVersion string
|
|
CreateTimestamp uint32
|
|
EventHeaderLength uint8
|
|
EventTypeHeaderLengths []uint8
|
|
ServerDetails ServerDetails
|
|
}
|
|
|
|
// ServerDetails contains server feature details.
|
|
type ServerDetails struct {
|
|
Flavor Flavor
|
|
Version int
|
|
ChecksumAlgorithm ChecksumAlgorithm
|
|
}
|
|
|
|
// FormatDescriptionEvent contains server details and binary log format
|
|
// description. It is usually the first event in a log file.
|
|
type FormatDescriptionEvent struct {
|
|
FormatDescription
|
|
}
|
|
|
|
// Flavor defines the specific kind of MySQL-like database.
|
|
type Flavor string
|
|
|
|
// ChecksumAlgorithm is a checksum algorithm is the one used by the server.
|
|
type ChecksumAlgorithm byte
|
|
|
|
const (
|
|
// FlavorMySQL is the MySQL db flavor.
|
|
FlavorMySQL = "MySQL"
|
|
|
|
// ChecksumAlgorithmNone means no checksum appened.
|
|
ChecksumAlgorithmNone ChecksumAlgorithm = 0x00
|
|
// ChecksumAlgorithmCRC32 used to append a 4 byte checksum at the end.
|
|
ChecksumAlgorithmCRC32 ChecksumAlgorithm = 0x01
|
|
// ChecksumAlgorithmUndefined is used when checksum algorithm is not known.
|
|
ChecksumAlgorithmUndefined ChecksumAlgorithm = 0xFF
|
|
)
|
|
|
|
// Decode decodes given buffer into a format description event.
|
|
// Spec: https://dev.mysql.com/doc/internals/en/format-description-event.html
|
|
func (e *FormatDescriptionEvent) Decode(data []byte) error {
|
|
buf := tools.NewBuffer(data)
|
|
e.Version = buf.ReadUint16()
|
|
e.ServerVersion = trimString(buf.ReadStringVarLen(50))
|
|
e.CreateTimestamp = buf.ReadUint32()
|
|
e.EventHeaderLength = buf.ReadUint8()
|
|
e.EventTypeHeaderLengths = buf.ReadStringEOF()
|
|
e.ServerDetails = ServerDetails{
|
|
Flavor: FlavorMySQL,
|
|
Version: parseVersionNumber(e.ServerVersion),
|
|
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]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HeaderLen returns length of event header.
|
|
func (fd FormatDescription) HeaderLen() int {
|
|
const defaultHeaderLength = 19
|
|
if fd.EventHeaderLength > 0 {
|
|
return int(fd.EventHeaderLength)
|
|
}
|
|
return defaultHeaderLength
|
|
}
|
|
|
|
// PostHeaderLen returns length of a post-header for a given event type.
|
|
func (fd FormatDescription) PostHeaderLen(et EventType) int {
|
|
return int(fd.EventTypeHeaderLengths[et-1])
|
|
}
|
|
|
|
// TableIDSize returns table ID size for a given event type.
|
|
func (fd FormatDescription) TableIDSize(et EventType) int {
|
|
if fd.PostHeaderLen(et) == 6 {
|
|
return 4
|
|
}
|
|
return 6
|
|
}
|
|
|
|
func (ca ChecksumAlgorithm) String() string {
|
|
switch ca {
|
|
case ChecksumAlgorithmNone:
|
|
return "None"
|
|
case ChecksumAlgorithmCRC32:
|
|
return "CRC32"
|
|
case ChecksumAlgorithmUndefined:
|
|
return "Undefined"
|
|
default:
|
|
return fmt.Sprintf("Unknown(%d)", ca)
|
|
}
|
|
}
|
|
|
|
// parseVersionNumber turns string version into a number just like the library
|
|
// mysql_get_server_version function does.
|
|
// Example: 5.7.19-log gets represented as 50719
|
|
// Spec: https://dev.mysql.com/doc/refman/8.0/en/mysql-get-server-version.html
|
|
func parseVersionNumber(v string) int {
|
|
tokens := strings.Split(v, ".")
|
|
major, _ := strconv.Atoi(tokens[0])
|
|
minor, _ := strconv.Atoi(tokens[1])
|
|
var patch int
|
|
for i, c := range tokens[2] {
|
|
if c < '0' || c > '9' {
|
|
patch, _ = strconv.Atoi(tokens[2][:i])
|
|
break
|
|
}
|
|
}
|
|
return major*10000 + minor*100 + patch
|
|
}
|
|
|
|
func trimString(str []byte) string {
|
|
for i, c := range str {
|
|
if c == 0x00 {
|
|
return string(str[:i])
|
|
}
|
|
}
|
|
return string(str)
|
|
}
|