Merge pull request #668 from youtube/mysql56

Implement GTID and GTIDSet for MySQL 5.6.
This commit is contained in:
Anthony Yeh 2015-05-06 10:33:55 -07:00
Родитель b08603e4c8 927cddc3f7
Коммит 3b90724a36
20 изменённых файлов: 1162 добавлений и 100 удалений

Просмотреть файл

@ -0,0 +1,6 @@
# Options for enabling GTID
# https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-howto.html
gtid_mode = ON
log_bin
log_slave_updates
enforce_gtid_consistency

Двоичные данные
data/bootstrap/mysql-db-dir_5.6.24.tbz

Двоичный файл не отображается.

Просмотреть файл

@ -31,7 +31,8 @@ mysqlctl $mysqlctl_args shutdown
newfile=mysql-db-dir_$(cat $tablet_dir/data/mysql_upgrade_info).tbz
echo Creating new bootstrap file: $newfile
(cd $tablet_dir && tar -jcf data.tbz data innodb)
# Remove auto-generated UUID file, if present (MySQL 5.6).
(cd $tablet_dir && rm -f data/auto.cnf && tar -jcf data.tbz data innodb)
mv $tablet_dir/data.tbz ./$newfile
echo Removing tablet directory

Просмотреть файл

@ -32,7 +32,18 @@ dbconfig_flags="\
# Set up environment.
export LD_LIBRARY_PATH=$VTROOT/dist/vt-zookeeper-3.3.5/lib:$LD_LIBRARY_PATH
export ZK_CLIENT_CONFIG=$script_root/zk-client-conf.json
export EXTRA_MY_CNF=$VTROOT/config/mycnf/master_mariadb.cnf
case "$MYSQL_FLAVOR" in
"MySQL56")
bootstrap_archive=mysql-db-dir_5.6.24.tbz
export EXTRA_MY_CNF=$VTROOT/config/mycnf/master_mysql56.cnf
;;
"MariaDB")
bootstrap_archive=mysql-db-dir_10.0.13-MariaDB.tbz
export EXTRA_MY_CNF=$VTROOT/config/mycnf/master_mariadb.cnf
;;
esac
mkdir -p $VTDATAROOT/tmp
# Try to find mysqld_safe on PATH.
@ -63,7 +74,7 @@ for uid_index in 0 1 2; do
echo "Starting MySQL for tablet $alias..."
$VTROOT/bin/mysqlctl -log_dir $VTDATAROOT/tmp -tablet_uid $uid $dbconfig_flags \
-mysql_port $mysql_port \
init -bootstrap_archive mysql-db-dir_10.0.13-MariaDB.tbz
init -bootstrap_archive $bootstrap_archive
$VT_MYSQL_ROOT/bin/mysql -u vt_dba -S $VTDATAROOT/$tablet_dir/mysql.sock \
-e "CREATE DATABASE IF NOT EXISTS vt_$keyspace"

Просмотреть файл

@ -21,8 +21,13 @@ import (
var (
binlogStreamerErrors = stats.NewCounters("BinlogStreamerErrors")
ClientEOF = fmt.Errorf("binlog stream consumer ended the reply stream")
ServerEOF = fmt.Errorf("binlog stream connection was closed by mysqld")
// ErrClientEOF is returned by BinlogStreamer if the stream ended because the
// consumer of the stream indicated it doesn't want any more events.
ErrClientEOF = fmt.Errorf("binlog stream consumer ended the reply stream")
// ErrServerEOF is returned by BinlogStreamer if the stream ended because the
// connection to the mysqld server was lost, or the stream was terminated by
// mysqld.
ErrServerEOF = fmt.Errorf("binlog stream connection was closed by mysqld")
// statementPrefixes are normal sql statement prefixes.
statementPrefixes = map[string]int{
@ -132,8 +137,8 @@ func (bls *BinlogStreamer) Stream(ctx *sync2.ServiceContext) (err error) {
// at a time, and groups them into transactions. It is called from within the
// service function launched by Stream().
//
// If the sendTransaction func returns io.EOF, parseEvents returns ClientEOF.
// If the events channel is closed, parseEvents returns ServerEOF.
// If the sendTransaction func returns io.EOF, parseEvents returns ErrClientEOF.
// If the events channel is closed, parseEvents returns ErrServerEOF.
func (bls *BinlogStreamer) parseEvents(ctx *sync2.ServiceContext, events <-chan proto.BinlogEvent) (myproto.ReplicationPosition, error) {
var statements []proto.Statement
var format proto.BinlogFormat
@ -162,7 +167,7 @@ func (bls *BinlogStreamer) parseEvents(ctx *sync2.ServiceContext, events <-chan
}
if err = bls.sendTransaction(trans); err != nil {
if err == io.EOF {
return ClientEOF
return ErrClientEOF
}
return fmt.Errorf("send reply error: %v", err)
}
@ -181,7 +186,7 @@ func (bls *BinlogStreamer) parseEvents(ctx *sync2.ServiceContext, events <-chan
if !ok {
// events channel has been closed, which means the connection died.
log.Infof("reached end of binlog event stream")
return pos, ServerEOF
return pos, ErrServerEOF
}
case <-ctx.ShuttingDown:
log.Infof("stopping early due to BinlogStreamer service shutdown")
@ -217,7 +222,10 @@ func (bls *BinlogStreamer) parseEvents(ctx *sync2.ServiceContext, events <-chan
}
// Strip the checksum, if any. We don't actually verify the checksum, so discard it.
ev, _ = ev.StripChecksum(format)
ev, _, err = ev.StripChecksum(format)
if err != nil {
return pos, fmt.Errorf("can't strip checksum from binlog event: %v, event data: %#v", err, ev)
}
// Update the GTID if the event has one. The actual event type could be
// something special like GTID_EVENT (MariaDB, MySQL 5.6), or it could be

Просмотреть файл

@ -49,17 +49,23 @@ func (fakeEvent) IntVar(proto.BinlogFormat) (string, uint64, error) {
func (fakeEvent) Rand(proto.BinlogFormat) (uint64, uint64, error) {
return 0, 0, errors.New("not a rand")
}
func (ev fakeEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (ev fakeEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type invalidEvent struct{ fakeEvent }
func (invalidEvent) IsValid() bool { return false }
func (ev invalidEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (invalidEvent) IsValid() bool { return false }
func (ev invalidEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type rotateEvent struct{ fakeEvent }
func (rotateEvent) IsRotate() bool { return true }
func (ev rotateEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (rotateEvent) IsRotate() bool { return true }
func (ev rotateEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type formatEvent struct{ fakeEvent }
@ -67,15 +73,17 @@ func (formatEvent) IsFormatDescription() bool { return true }
func (formatEvent) Format() (proto.BinlogFormat, error) {
return proto.BinlogFormat{FormatVersion: 1}, nil
}
func (ev formatEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (ev formatEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type invalidFormatEvent struct{ formatEvent }
func (invalidFormatEvent) Format() (proto.BinlogFormat, error) {
return proto.BinlogFormat{}, errors.New("invalid format event")
}
func (ev invalidFormatEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) {
return ev, nil
func (ev invalidFormatEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type queryEvent struct {
@ -87,21 +95,25 @@ func (queryEvent) IsQuery() bool { return true }
func (ev queryEvent) Query(proto.BinlogFormat) (proto.Query, error) {
return ev.query, nil
}
func (ev queryEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (ev queryEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type invalidQueryEvent struct{ queryEvent }
func (invalidQueryEvent) Query(proto.BinlogFormat) (proto.Query, error) {
return proto.Query{}, errors.New("invalid query event")
}
func (ev invalidQueryEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) {
return ev, nil
func (ev invalidQueryEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type xidEvent struct{ fakeEvent }
func (xidEvent) IsXID() bool { return true }
func (ev xidEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (xidEvent) IsXID() bool { return true }
func (ev xidEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type intVarEvent struct {
fakeEvent
@ -113,15 +125,17 @@ func (intVarEvent) IsIntVar() bool { return true }
func (ev intVarEvent) IntVar(proto.BinlogFormat) (string, uint64, error) {
return ev.name, ev.value, nil
}
func (ev intVarEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) { return ev, nil }
func (ev intVarEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
type invalidIntVarEvent struct{ intVarEvent }
func (invalidIntVarEvent) IntVar(proto.BinlogFormat) (string, uint64, error) {
return "", 0, errors.New("invalid intvar event")
}
func (ev invalidIntVarEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte) {
return ev, nil
func (ev invalidIntVarEvent) StripChecksum(proto.BinlogFormat) (proto.BinlogEvent, []byte, error) {
return ev, nil, nil
}
// sample MariaDB event data
@ -179,7 +193,7 @@ func TestBinlogStreamerParseEventsXID(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -223,7 +237,7 @@ func TestBinlogStreamerParseEventsCommit(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -271,7 +285,7 @@ func TestBinlogStreamerParseEventsClientEOF(t *testing.T) {
queryEvent{query: proto.Query{Database: "vt_test_keyspace", Sql: []byte("insert into vt_a(eid, id) values (1, 1) /* _stream vt_a (eid id ) (1 1 ); */")}},
xidEvent{},
}
want := ClientEOF
want := ErrClientEOF
events := make(chan proto.BinlogEvent)
@ -296,7 +310,7 @@ func TestBinlogStreamerParseEventsClientEOF(t *testing.T) {
}
func TestBinlogStreamerParseEventsServerEOF(t *testing.T) {
want := ServerEOF
want := ErrServerEOF
events := make(chan proto.BinlogEvent)
close(events)
@ -563,7 +577,7 @@ func TestBinlogStreamerParseEventsRollback(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -612,7 +626,7 @@ func TestBinlogStreamerParseEventsDMLWithoutBegin(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -662,7 +676,7 @@ func TestBinlogStreamerParseEventsBeginWithoutCommit(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -708,7 +722,7 @@ func TestBinlogStreamerParseEventsSetInsertID(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -787,7 +801,7 @@ func TestBinlogStreamerParseEventsOtherDB(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -832,7 +846,7 @@ func TestBinlogStreamerParseEventsOtherDBBegin(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -864,7 +878,7 @@ func TestBinlogStreamerParseEventsBeginAgain(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
after := binlogStreamerErrors.Counts()["ParseEvents"]
@ -908,7 +922,7 @@ func TestBinlogStreamerParseEventsMariadbBeginGTID(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}
@ -951,7 +965,7 @@ func TestBinlogStreamerParseEventsMariadbStandaloneGTID(t *testing.T) {
_, err := bls.parseEvents(ctx, events)
return err
})
if err := svm.Join(); err != ServerEOF {
if err := svm.Join(); err != ErrServerEOF {
t.Errorf("unexpected error: %v", err)
}

Просмотреть файл

@ -78,7 +78,7 @@ type BinlogEvent interface {
// StripChecksum returns the checksum and a modified event with the checksum
// stripped off, if any. If there is no checksum, it returns the same event
// and a nil checksum.
StripChecksum(BinlogFormat) (ev BinlogEvent, checksum []byte)
StripChecksum(BinlogFormat) (ev BinlogEvent, checksum []byte, err error)
}
// BinlogFormat contains relevant data from the FORMAT_DESCRIPTION_EVENT.

Просмотреть файл

@ -264,3 +264,13 @@ func (ev binlogEvent) Rand(f blproto.BinlogFormat) (seed1 uint64, seed2 uint64,
func (ev binlogEvent) IsBeginGTID(f blproto.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
)

Просмотреть файл

@ -264,28 +264,21 @@ func (ev mariadbBinlogEvent) Format() (f blproto.BinlogFormat, err error) {
}
// StripChecksum implements BinlogEvent.StripChecksum().
func (ev mariadbBinlogEvent) StripChecksum(f blproto.BinlogFormat) (blproto.BinlogEvent, []byte) {
func (ev mariadbBinlogEvent) StripChecksum(f blproto.BinlogFormat) (blproto.BinlogEvent, []byte, error) {
switch f.ChecksumAlgorithm {
case BinlogChecksumAlgOff, BinlogChecksumAlgUndef:
// There is no checksum.
return ev, nil
return ev, nil, nil
default:
// Checksum is the last 4 bytes of the event buffer.
data := ev.Bytes()
length := len(data)
checksum := data[length-4:]
data = data[:length-4]
return mariadbBinlogEvent{binlogEvent: binlogEvent(data)}, checksum
return mariadbBinlogEvent{binlogEvent: binlogEvent(data)}, checksum, nil
}
}
const (
// BinlogChecksumAlgOff indicates that checksums are supported but off.
BinlogChecksumAlgOff = 0
// BinlogChecksumAlgUndef indicates that checksums are not supported.
BinlogChecksumAlgUndef = 255
)
func init() {
registerFlavorBuiltin(mariadbFlavorID, &mariaDB10{})
}

Просмотреть файл

@ -139,14 +139,16 @@ func TestMariadbBinlogEventChecksumFormat(t *testing.T) {
func TestMariadbBinlogEventStripChecksum(t *testing.T) {
f, err := (mariadbBinlogEvent{binlogEvent: binlogEvent(mariadbChecksumFormatEvent)}).Format()
if err != nil {
t.Errorf("unexpected error: %v", err)
return
t.Fatalf("unexpected error: %v", err)
}
input := mariadbBinlogEvent{binlogEvent: binlogEvent(mariadbChecksumQueryEvent)}
wantEvent := mariadbBinlogEvent{binlogEvent: binlogEvent(mariadbChecksumStrippedQueryEvent)}
wantChecksum := []byte{0xce, 0x49, 0x7a, 0x53}
gotEvent, gotChecksum := input.StripChecksum(f)
gotEvent, gotChecksum, err := input.StripChecksum(f)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(gotEvent, wantEvent) || !reflect.DeepEqual(gotChecksum, wantChecksum) {
t.Errorf("%#v.StripChecksum() = (%v, %v), want (%v, %v)", input, gotEvent, gotChecksum, wantEvent, wantChecksum)
}
@ -155,13 +157,15 @@ func TestMariadbBinlogEventStripChecksum(t *testing.T) {
func TestMariadbBinlogEventStripChecksumNone(t *testing.T) {
f, err := (mariadbBinlogEvent{binlogEvent: binlogEvent(mariadbFormatEvent)}).Format()
if err != nil {
t.Errorf("unexpected error: %v", err)
return
t.Fatalf("unexpected error: %v", err)
}
input := mariadbBinlogEvent{binlogEvent: binlogEvent(mariadbStandaloneGTIDEvent)}
want := input
gotEvent, gotChecksum := input.StripChecksum(f)
gotEvent, gotChecksum, err := input.StripChecksum(f)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(gotEvent, want) || gotChecksum != nil {
t.Errorf("%#v.StripChecksum() = (%v, %v), want (%v, nil)", input, gotEvent, gotChecksum, want)
}

Просмотреть файл

@ -30,16 +30,16 @@ type GTID interface {
Flavor() string
// SourceServer returns the ID of the server that generated the transaction.
SourceServer() string
SourceServer() interface{}
// SequenceNumber returns the ID number that increases with each transaction.
// It is only valid to compare the sequence numbers of two GTIDs if they have
// the same domain value.
SequenceNumber() uint64
SequenceNumber() interface{}
// SequenceDomain returns the ID of the domain within which two sequence
// numbers can be meaningfully compared.
SequenceDomain() string
SequenceDomain() interface{}
// GTIDSet returns a GTIDSet of the same flavor as this GTID, containing only
// this GTID.

Просмотреть файл

@ -21,10 +21,7 @@ type GTIDSet interface {
// registered in the transactionSetParsers map.
Flavor() string
// Last returns the GTID of the most recent transaction in the set.
Last() GTID
// Contains returns true if the set contains the specified transaction.
// ContainsGTID returns true if the set contains the specified transaction.
ContainsGTID(GTID) bool
// Contains returns true if the set is a superset of another set.

Просмотреть файл

@ -421,15 +421,20 @@ type fakeGTID struct {
flavor, value string
}
func (f fakeGTID) String() string { return f.value }
func (f fakeGTID) Flavor() string { return f.flavor }
func (fakeGTID) SourceServer() string { return "" }
func (fakeGTID) SequenceNumber() uint64 { return 0 }
func (fakeGTID) SequenceDomain() string { return "" }
func (f fakeGTID) GTIDSet() GTIDSet { return nil }
func (f fakeGTID) String() string { return f.value }
func (f fakeGTID) Flavor() string { return f.flavor }
func (fakeGTID) SourceServer() interface{} { return int(1) }
func (fakeGTID) SequenceNumber() interface{} { return int(1) }
func (fakeGTID) SequenceDomain() interface{} { return int(1) }
func (f fakeGTID) GTIDSet() GTIDSet { return nil }
func (fakeGTID) Last() GTID { return nil }
func (fakeGTID) ContainsGTID(GTID) bool { return false }
func (fakeGTID) Contains(GTIDSet) bool { return false }
func (f fakeGTID) Equal(other GTIDSet) bool { return f == other.(fakeGTID) }
func (fakeGTID) AddGTID(GTID) GTIDSet { return nil }
func (fakeGTID) ContainsGTID(GTID) bool { return false }
func (fakeGTID) Contains(GTIDSet) bool { return false }
func (f fakeGTID) Equal(other GTIDSet) bool {
otherFake, ok := other.(fakeGTID)
if !ok {
return false
}
return f == otherFake
}
func (fakeGTID) AddGTID(GTID) GTIDSet { return nil }

Просмотреть файл

@ -75,17 +75,17 @@ func (gtid MariadbGTID) Flavor() string {
}
// SequenceDomain implements GTID.SequenceDomain().
func (gtid MariadbGTID) SequenceDomain() string {
return strconv.FormatUint(uint64(gtid.Domain), 10)
func (gtid MariadbGTID) SequenceDomain() interface{} {
return gtid.Domain
}
// SourceServer implements GTID.SourceServer().
func (gtid MariadbGTID) SourceServer() string {
return strconv.FormatUint(uint64(gtid.Server), 10)
func (gtid MariadbGTID) SourceServer() interface{} {
return gtid.Server
}
// SequenceNumber implements GTID.SequenceNumber().
func (gtid MariadbGTID) SequenceNumber() uint64 {
func (gtid MariadbGTID) SequenceNumber() interface{} {
return gtid.Sequence
}
@ -94,11 +94,6 @@ func (gtid MariadbGTID) GTIDSet() GTIDSet {
return gtid
}
// Last implements GTIDSet.Last().
func (gtid MariadbGTID) Last() GTID {
return gtid
}
// ContainsGTID implements GTIDSet.ContainsGTID().
func (gtid MariadbGTID) ContainsGTID(other GTID) bool {
if other == nil {

Просмотреть файл

@ -123,31 +123,31 @@ func TestMariaGTIDFlavor(t *testing.T) {
func TestMariaGTIDSequenceDomain(t *testing.T) {
input := MariadbGTID{Domain: 12, Server: 345, Sequence: 6789}
want := "12"
want := interface{}(uint32(12))
got := input.SequenceDomain()
if got != want {
t.Errorf("%#v.SequenceDomain() = '%v', want '%v'", input, got, want)
t.Errorf("%#v.SequenceDomain() = %#v, want %#v", input, got, want)
}
}
func TestMariaGTIDSourceServer(t *testing.T) {
input := MariadbGTID{Domain: 12, Server: 345, Sequence: 6789}
want := "345"
want := interface{}(uint32(345))
got := input.SourceServer()
if got != want {
t.Errorf("%#v.SourceServer() = '%v', want '%v'", input, got, want)
t.Errorf("%#v.SourceServer() = %#v, want %#v", input, got, want)
}
}
func TestMariaGTIDSequenceNumber(t *testing.T) {
input := MariadbGTID{Domain: 12, Server: 345, Sequence: 6789}
want := uint64(6789)
want := interface{}(uint64(6789))
got := input.SequenceNumber()
if got != want {
t.Errorf("%#v.SequenceNumber() = %v, want %v", input, got, want)
t.Errorf("%#v.SequenceNumber() = %#v, want %#v", input, got, want)
}
}
@ -161,16 +161,6 @@ func TestMariaGTIDGTIDSet(t *testing.T) {
}
}
func TestMariaGTIDLast(t *testing.T) {
input := MariadbGTID{Domain: 12, Server: 345, Sequence: 6789}
want := GTID(input)
got := input.Last()
if got != want {
t.Errorf("%#v.Last() = %#v, want %#v", input, got, want)
}
}
func TestMariaGTIDContainsLess(t *testing.T) {
input1 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 300}
input2 := MariadbGTID{Domain: 5, Server: 4727, Sequence: 700}

Просмотреть файл

@ -0,0 +1,114 @@
// Copyright 2015, 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 proto
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
)
const mysql56FlavorID = "MySQL56"
// parseMysql56GTID is registered as a GTID parser.
func parseMysql56GTID(s string) (GTID, error) {
// Split into parts.
parts := strings.Split(s, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID (%v): expecting UUID:Sequence", s)
}
// Parse Server ID.
sid, err := ParseSID(parts[0])
if err != nil {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID Server ID (%v): %v", parts[0], err)
}
// Parse Sequence number.
seq, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID Sequence number (%v): %v", parts[1], err)
}
return Mysql56GTID{Server: sid, Sequence: seq}, nil
}
// SID is the 16-byte unique ID of a MySQL 5.6 server.
type SID [16]byte
// String prints an SID in the form used by MySQL 5.6.
func (sid SID) String() string {
dst := []byte("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
hex.Encode(dst, sid[:4])
hex.Encode(dst[9:], sid[4:6])
hex.Encode(dst[14:], sid[6:8])
hex.Encode(dst[19:], sid[8:10])
hex.Encode(dst[24:], sid[10:16])
return string(dst)
}
// ParseSID parses an SID in the form used by MySQL 5.6.
func ParseSID(s string) (sid SID, err error) {
if len(s) != 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return sid, fmt.Errorf("invalid MySQL 5.6 SID %q", s)
}
// Drop the dashes so we can just check the error of Decode once.
b := make([]byte, 0, 32)
b = append(b, s[:8]...)
b = append(b, s[9:13]...)
b = append(b, s[14:18]...)
b = append(b, s[19:23]...)
b = append(b, s[24:]...)
if _, err := hex.Decode(sid[:], b); err != nil {
return sid, fmt.Errorf("invalid MySQL 5.6 SID %q: %v", s, err)
}
return sid, nil
}
// Mysql56GTID implements GTID
type Mysql56GTID struct {
// Server is the SID of the server that originally committed the transaction.
Server SID
// Sequence is the sequence number of the transaction within a given Server's
// scope.
Sequence int64
}
// String implements GTID.String().
func (gtid Mysql56GTID) String() string {
return fmt.Sprintf("%s:%d", gtid.Server, gtid.Sequence)
}
// Flavor implements GTID.Flavor().
func (gtid Mysql56GTID) Flavor() string {
return mysql56FlavorID
}
// SequenceDomain implements GTID.SequenceDomain().
func (gtid Mysql56GTID) SequenceDomain() interface{} {
return nil
}
// SourceServer implements GTID.SourceServer().
func (gtid Mysql56GTID) SourceServer() interface{} {
return gtid.Server
}
// SequenceNumber implements GTID.SequenceNumber().
func (gtid Mysql56GTID) SequenceNumber() interface{} {
return gtid.Sequence
}
// GTIDSet implements GTID.GTIDSet().
func (gtid Mysql56GTID) GTIDSet() GTIDSet {
return Mysql56GTIDSet{}.AddGTID(gtid)
}
func init() {
gtidParsers[mysql56FlavorID] = parseMysql56GTID
}

Просмотреть файл

@ -0,0 +1,345 @@
// Copyright 2015, 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 proto
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
"strconv"
"strings"
)
type interval struct {
start, end int64
}
func (iv interval) contains(other interval) bool {
return iv.start <= other.start && other.end <= iv.end
}
type intervalList []interval
// Len implements sort.Interface.
func (s intervalList) Len() int { return len(s) }
// Less implements sort.Interface.
func (s intervalList) Less(i, j int) bool { return s[i].start < s[j].start }
// Swap implements sort.Interface.
func (s intervalList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func parseInterval(s string) (interval, error) {
parts := strings.Split(s, "-")
start, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return interval{}, fmt.Errorf("invalid interval (%q): %v", s, err)
}
if start < 1 {
return interval{}, fmt.Errorf("invalid interval (%q): start must be > 0", s)
}
switch len(parts) {
case 1:
return interval{start: start, end: start}, nil
case 2:
end, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return interval{}, fmt.Errorf("invalid interval (%q): %v", s, err)
}
return interval{start: start, end: end}, nil
default:
return interval{}, fmt.Errorf("invalid interval (%q): expected start-end or single number", s)
}
}
// parseMysql56GTIDSet is registered as a GTIDSet parser.
//
// https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
func parseMysql56GTIDSet(s string) (GTIDSet, error) {
set := Mysql56GTIDSet{}
// gtid_set: uuid_set [, uuid_set] ...
for _, uuidSet := range strings.Split(s, ",") {
uuidSet = strings.TrimSpace(uuidSet)
if uuidSet == "" {
continue
}
// uuid_set: uuid:interval[:interval]...
parts := strings.Split(uuidSet, ":")
if len(parts) < 2 {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID set (%q): expected uuid:interval", s)
}
// Parse Server ID.
sid, err := ParseSID(parts[0])
if err != nil {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID set (%q): %v", s, err)
}
// Parse Intervals.
intervals := make([]interval, 0, len(parts)-1)
for _, part := range parts[1:] {
iv, err := parseInterval(part)
if err != nil {
return nil, fmt.Errorf("invalid MySQL 5.6 GTID set (%q): %v", s, err)
}
if iv.end < iv.start {
// According to MySQL 5.6 code:
// "The end of an interval may be 0, but any interval that has an
// endpoint that is smaller than the start is discarded."
continue
}
intervals = append(intervals, iv)
}
if len(intervals) == 0 {
// We might have discarded all the intervals.
continue
}
// Internally we expect intervals to be stored in order.
sort.Sort(intervalList(intervals))
set[sid] = intervals
}
return set, nil
}
// Mysql56GTIDSet implements GTIDSet for MySQL 5.6.
type Mysql56GTIDSet map[SID][]interval
// SIDs returns a sorted list of SIDs in the set.
func (set Mysql56GTIDSet) SIDs() []SID {
sids := make([]SID, 0, len(set))
for sid := range set {
sids = append(sids, sid)
}
sort.Sort(sidList(sids))
return sids
}
type sidList []SID
// Len implements sort.Interface.
func (s sidList) Len() int { return len(s) }
// Less implements sort.Interface.
func (s sidList) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
// Swap implements sort.Interface.
func (s sidList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// String implements GTIDSet.
func (set Mysql56GTIDSet) String() string {
buf := &bytes.Buffer{}
for i, sid := range set.SIDs() {
if i != 0 {
buf.WriteByte(',')
}
buf.WriteString(sid.String())
for _, interval := range set[sid] {
buf.WriteByte(':')
buf.WriteString(strconv.FormatInt(interval.start, 10))
if interval.end != interval.start {
buf.WriteByte('-')
buf.WriteString(strconv.FormatInt(interval.end, 10))
}
}
}
return buf.String()
}
// Flavor implements GTIDSet.
func (Mysql56GTIDSet) Flavor() string { return mysql56FlavorID }
// ContainsGTID implements GTIDSet.
func (set Mysql56GTIDSet) ContainsGTID(gtid GTID) bool {
gtid56, ok := gtid.(Mysql56GTID)
if !ok {
return false
}
for _, iv := range set[gtid56.Server] {
if iv.start > gtid56.Sequence {
// We assume intervals are sorted, so we can skip the rest.
return false
}
if gtid56.Sequence <= iv.end {
// Now we know that: start <= Sequence <= end.
return true
}
}
// Server wasn't in the set, or no interval contained gtid.
return false
}
// Contains implements GTIDSet.
func (set Mysql56GTIDSet) Contains(other GTIDSet) bool {
other56, ok := other.(Mysql56GTIDSet)
if !ok {
return false
}
// Check each SID in the other set.
for sid, otherIntervals := range other56 {
i := 0
intervals := set[sid]
count := len(intervals)
// Check each interval for this SID in the other set.
for _, iv := range otherIntervals {
// Check that interval against each of our intervals.
// Intervals are monotonically increasing,
// so we don't need to reset the index each time.
for {
if i >= count {
// We ran out of intervals to check against.
return false
}
if intervals[i].contains(iv) {
// Yes it's covered. Go on to the next one.
break
}
i++
}
}
}
// No uncovered intervals were found.
return true
}
// Equal implements GTIDSet.
func (set Mysql56GTIDSet) Equal(other GTIDSet) bool {
other56, ok := other.(Mysql56GTIDSet)
if !ok {
return false
}
// Check for same number of SIDs.
if len(set) != len(other56) {
return false
}
// Compare each SID.
for sid, intervals := range set {
otherIntervals := other56[sid]
// Check for same number of intervals.
if len(intervals) != len(otherIntervals) {
return false
}
// Compare each interval.
// Since intervals are sorted, they have to be in the same order.
for i, iv := range intervals {
if iv != otherIntervals[i] {
return false
}
}
}
// No discrepancies were found.
return true
}
// AddGTID implements GTIDSet.
func (set Mysql56GTIDSet) AddGTID(gtid GTID) GTIDSet {
gtid56, ok := gtid.(Mysql56GTID)
if !ok {
return set
}
// Make a copy and add the new GTID in the proper place.
// This function is not supposed to modify the original set.
newSet := make(Mysql56GTIDSet)
added := false
for sid, intervals := range set {
newIntervals := make([]interval, 0, len(intervals))
if sid == gtid56.Server {
// Look for the right place to add this GTID.
for _, iv := range intervals {
if !added {
switch {
case gtid56.Sequence == iv.start-1:
// Expand the interval at the beginning.
iv.start = gtid56.Sequence
added = true
case gtid56.Sequence == iv.end+1:
// Expand the interval at the end.
iv.end = gtid56.Sequence
added = true
case gtid56.Sequence < iv.start-1:
// The next interval is beyond the new GTID, but it can't
// be expanded, so we have to insert a new interval.
newIntervals = append(newIntervals, interval{start: gtid56.Sequence, end: gtid56.Sequence})
added = true
}
}
// Check if this interval can be merged with the previous one.
count := len(newIntervals)
if count != 0 && iv.start == newIntervals[count-1].end+1 {
// Merge instead of appending.
newIntervals[count-1].end = iv.end
} else {
// Can't be merged.
newIntervals = append(newIntervals, iv)
}
}
} else {
// Just copy everything.
newIntervals = append(newIntervals, intervals...)
}
newSet[sid] = newIntervals
}
if !added {
// There wasn't any place to insert the new GTID, so just append it
// as a new interval.
newSet[gtid56.Server] = append(newSet[gtid56.Server], interval{start: gtid56.Sequence, end: gtid56.Sequence})
}
return newSet
}
// SIDBlock returns the binary encoding of a MySQL 5.6 GTID set as expected
// by internal commands that refer to an "SID block".
//
// e.g. https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
func (set Mysql56GTIDSet) SIDBlock() []byte {
buf := &bytes.Buffer{}
// Number of SIDs.
binary.Write(buf, binary.LittleEndian, uint64(len(set)))
for _, sid := range set.SIDs() {
buf.Write(sid[:])
// Number of intervals.
intervals := set[sid]
binary.Write(buf, binary.LittleEndian, uint64(len(intervals)))
for _, iv := range intervals {
binary.Write(buf, binary.LittleEndian, iv.start)
binary.Write(buf, binary.LittleEndian, iv.end)
}
}
return buf.Bytes()
}
func init() {
gtidSetParsers[mysql56FlavorID] = parseMysql56GTIDSet
}

Просмотреть файл

@ -0,0 +1,430 @@
// Copyright 2015, 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 proto
import (
"reflect"
"sort"
"strings"
"testing"
)
func TestSortSIDList(t *testing.T) {
input := []SID{
{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
}
want := []SID{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
}
sort.Sort(sidList(input))
if !reflect.DeepEqual(input, want) {
t.Errorf("got %#v, want %#v", input, want)
}
}
func TestParseMysql56GTIDSet(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255}
table := map[string]Mysql56GTIDSet{
// Empty
"": Mysql56GTIDSet{},
// Simple case
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5": Mysql56GTIDSet{
sid1: []interval{{1, 5}},
},
// Capital hex chars
"00010203-0405-0607-0809-0A0B0C0D0E0F:1-5": Mysql56GTIDSet{
sid1: []interval{{1, 5}},
},
// Interval with same start and end
"00010203-0405-0607-0809-0a0b0c0d0e0f:12": Mysql56GTIDSet{
sid1: []interval{{12, 12}},
},
// Multiple intervals
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
},
// Multiple intervals, out of oder
"00010203-0405-0607-0809-0a0b0c0d0e0f:10-20:1-5": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
},
// Intervals with end < start are discarded by MySQL 5.6
"00010203-0405-0607-0809-0a0b0c0d0e0f:8-7": Mysql56GTIDSet{},
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:8-7:10-20": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
},
// Multiple SIDs
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20,00010203-0405-0607-0809-0a0b0c0d0eff:1-5:50": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
sid2: []interval{{1, 5}, {50, 50}},
},
// Multiple SIDs with space around the comma
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20, 00010203-0405-0607-0809-0a0b0c0d0eff:1-5:50": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
sid2: []interval{{1, 5}, {50, 50}},
},
}
for input, want := range table {
got, err := parseMysql56GTIDSet(input)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !got.Equal(want) {
t.Errorf("parseMysql56GTIDSet(%#v) = %#v, want %#v", input, got, want)
}
}
}
func TestParseMysql56GTIDSetInvalid(t *testing.T) {
table := []string{
// No intervals
"00010203-0405-0607-0809-0a0b0c0d0e0f",
// Invalid SID
"00010203-0405-060X-0809-0a0b0c0d0e0f:1-5",
// Invalid intervals
"00010203-0405-0607-0809-0a0b0c0d0e0f:0-5",
"00010203-0405-0607-0809-0a0b0c0d0e0f:-5",
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-2-3",
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-",
}
for _, input := range table {
_, err := parseMysql56GTIDSet(input)
if err == nil {
t.Errorf("parseMysql56GTIDSet(%#v) expected error, got none", err)
}
}
}
func TestMysql56GTIDSetString(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255}
table := map[string]Mysql56GTIDSet{
// Simple case
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5": Mysql56GTIDSet{
sid1: []interval{{1, 5}},
},
// Interval with same start and end
"00010203-0405-0607-0809-0a0b0c0d0e0f:12": Mysql56GTIDSet{
sid1: []interval{{12, 12}},
},
// Multiple intervals
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
},
// Multiple SIDs
"00010203-0405-0607-0809-0a0b0c0d0e0f:1-5:10-20,00010203-0405-0607-0809-0a0b0c0d0eff:1-5:50": Mysql56GTIDSet{
sid1: []interval{{1, 5}, {10, 20}},
sid2: []interval{{1, 5}, {50, 50}},
},
}
for want, input := range table {
got := strings.ToLower(input.String())
if got != want {
t.Errorf("%#v.String() = %#v, want %#v", input, got, want)
}
}
}
func TestMysql56GTIDSetFlavor(t *testing.T) {
input := Mysql56GTIDSet{}
if got, want := input.Flavor(), "MySQL56"; got != want {
t.Errorf("%#v.Flavor() = %#v, want %#v", input, got, want)
}
}
func TestMysql56GTIDSetContainsGTID(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16}
sid3 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}
set := Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}},
}
table := map[GTID]bool{
fakeGTID{}: false,
Mysql56GTID{sid1, 1}: false,
Mysql56GTID{sid1, 19}: false,
Mysql56GTID{sid1, 20}: true,
Mysql56GTID{sid1, 23}: true,
Mysql56GTID{sid1, 30}: true,
Mysql56GTID{sid1, 31}: false,
Mysql56GTID{sid2, 1}: true,
Mysql56GTID{sid2, 10}: false,
Mysql56GTID{sid2, 50}: true,
Mysql56GTID{sid2, 51}: false,
Mysql56GTID{sid3, 1}: false,
}
for input, want := range table {
if got := set.ContainsGTID(input); got != want {
t.Errorf("ContainsGTID(%#v) = %#v, want %#v", input, got, want)
}
}
}
func TestMysql56GTIDSetContains(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16}
sid3 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}
// The set to test against.
set := Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
}
// Test cases that should return Contains() = true.
contained := []Mysql56GTIDSet{
// The set should contain itself.
set,
// Every set contains the empty set.
Mysql56GTIDSet{},
// Simple case
Mysql56GTIDSet{sid1: []interval{{25, 30}}},
// Multiple intervals
Mysql56GTIDSet{sid2: []interval{{1, 2}, {4, 5}, {60, 70}}},
// Multiple SIDs
Mysql56GTIDSet{
sid1: []interval{{25, 30}, {35, 37}},
sid2: []interval{{1, 5}},
},
}
for _, other := range contained {
if !set.Contains(other) {
t.Errorf("Contains(%#v) = false, want true", other)
}
}
// Test cases that should return Contains() = false.
notContained := []GTIDSet{
// Wrong flavor is not contained.
fakeGTID{},
// Simple cases
Mysql56GTIDSet{sid1: []interval{{1, 5}}},
Mysql56GTIDSet{sid1: []interval{{10, 19}}},
// Overlapping intervals
Mysql56GTIDSet{sid1: []interval{{10, 20}}},
Mysql56GTIDSet{sid1: []interval{{10, 25}}},
Mysql56GTIDSet{sid1: []interval{{25, 31}}},
Mysql56GTIDSet{sid1: []interval{{30, 31}}},
// Multiple intervals
Mysql56GTIDSet{sid1: []interval{{20, 30}, {34, 34}}},
// Multiple SIDs
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {36, 36}},
sid2: []interval{{3, 5}, {55, 60}},
},
// SID is missing entirely
Mysql56GTIDSet{sid3: []interval{{1, 5}}},
}
for _, other := range notContained {
if set.Contains(other) {
t.Errorf("Contains(%#v) = true, want false", other)
}
}
}
func TestMysql56GTIDSetEqual(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16}
sid3 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}
// The set to test against.
set := Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
}
// Test cases that should return Equal() = true.
equal := []Mysql56GTIDSet{
// Same underlying map instance
set,
// Different instance, same data
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
}
for _, other := range equal {
if !set.Equal(other) {
t.Errorf("%#v.Equal(%#v) = false, want true", set, other)
}
// Equality should be transitive.
if !other.Equal(set) {
t.Errorf("%#v.Equal(%#v) = false, want true", other, set)
}
}
// Test cases that should return Equal() = false.
notEqual := []GTIDSet{
// Wrong flavor is not equal.
fakeGTID{},
// Empty set
Mysql56GTIDSet{},
// Interval changed
Mysql56GTIDSet{
sid1: []interval{{20, 31}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// Interval added
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {32, 33}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// Interval removed
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {60, 70}},
},
// Different SID, same intervals
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid3: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// SID added
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
sid3: []interval{{1, 5}},
},
// SID removed
Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
},
}
for _, other := range notEqual {
if set.Equal(other) {
t.Errorf("%#v.Equal(%#v) = true, want false", set, other)
}
// Equality should be transitive.
if other.Equal(set) {
t.Errorf("%#v.Equal(%#v) = true, want false", other, set)
}
}
}
func TestMysql56GTIDSetAddGTID(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16}
sid3 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}
// The set to test against.
set := Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
}
table := map[GTID]Mysql56GTIDSet{
// Adding wrong flavor is a no-op.
fakeGTID{}: set,
// New interval beginning
Mysql56GTID{Server: sid1, Sequence: 1}: Mysql56GTIDSet{
sid1: []interval{{1, 1}, {20, 30}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// New interval middle
Mysql56GTID{Server: sid1, Sequence: 32}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {32, 32}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// New interval end
Mysql56GTID{Server: sid1, Sequence: 50}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}, {42, 45}, {50, 50}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// Extend interval start
Mysql56GTID{Server: sid2, Sequence: 49}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {49, 50}, {60, 70}},
},
// Extend interval end
Mysql56GTID{Server: sid2, Sequence: 51}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {50, 51}, {60, 70}},
},
// Merge intervals
Mysql56GTID{Server: sid1, Sequence: 41}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 45}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
},
// Different SID
Mysql56GTID{Server: sid3, Sequence: 1}: Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}, {42, 45}},
sid2: []interval{{1, 5}, {50, 50}, {60, 70}},
sid3: []interval{{1, 1}},
},
}
for input, want := range table {
if got := set.AddGTID(input); !got.Equal(want) {
t.Errorf("AddGTID(%#v) = %#v, want %#v", input, got, want)
}
}
}
func TestMysql56GTIDSetSIDBlock(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
sid2 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16}
input := Mysql56GTIDSet{
sid1: []interval{{20, 30}, {35, 40}},
sid2: []interval{{1, 5}},
}
want := []byte{
// n_sids
2, 0, 0, 0, 0, 0, 0, 0,
// sid1
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// sid1: n_intervals
2, 0, 0, 0, 0, 0, 0, 0,
// sid1: interval 1 start
20, 0, 0, 0, 0, 0, 0, 0,
// sid1: interval 1 end
30, 0, 0, 0, 0, 0, 0, 0,
// sid1: interval 2 start
35, 0, 0, 0, 0, 0, 0, 0,
// sid1: interval 2 end
40, 0, 0, 0, 0, 0, 0, 0,
// sid2
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16,
// sid2: n_intervals
1, 0, 0, 0, 0, 0, 0, 0,
// sid2: interval 1 start
1, 0, 0, 0, 0, 0, 0, 0,
// sid2: interval 1 end
5, 0, 0, 0, 0, 0, 0, 0,
}
if got := input.SIDBlock(); !reflect.DeepEqual(got, want) {
t.Errorf("%#v.SIDBlock() = %#v, want %#v", input, got, want)
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// Copyright 2015, 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 proto
import (
"strings"
"testing"
)
func TestParseMysql56GTID(t *testing.T) {
input := "00010203-0405-0607-0809-0A0B0C0D0E0F:56789"
want := Mysql56GTID{
Server: SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
Sequence: 56789,
}
got, err := parseMysql56GTID(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != want {
t.Errorf("parseMysql56GTID(%#v) = %#v, want %#v", input, got, want)
}
}
func TestParseMysql56GTIDInvalid(t *testing.T) {
table := []string{
"",
"00010203-0405-0607-0809-0A0B0C0D0E0F",
"00010203-0405-0607-0809-0A0B0C0D0E0F:1-5",
"00010203-0405-0607-0809-0A0B0C0D0E0F:1:2",
"00010203-0405-0607-0809-0A0B0C0D0E0X:1",
}
for _, input := range table {
_, err := parseMysql56GTID(input)
if err == nil {
t.Errorf("parseMysql56GTID(%#v): expected error, got none", input)
}
}
}
func TestSIDString(t *testing.T) {
input := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
want := "00010203-0405-0607-0809-0a0b0c0d0e0f"
if got := strings.ToLower(input.String()); got != want {
t.Errorf("%#v.String() = %#v, want %#v", input, got, want)
}
}
func TestParseSID(t *testing.T) {
input := "00010203-0405-0607-0809-0A0B0C0D0E0F"
want := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
got, err := ParseSID(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != want {
t.Errorf("ParseSID(%#v) = %#v, want %#v", input, got, want)
}
}
func TestParseSIDInvalid(t *testing.T) {
table := []string{
"123",
"x",
"00010203-0405-0607-0809-0A0B0C0D0E0x",
"00010203-0405-0607-080900A0B0C0D0E0F",
}
for _, input := range table {
_, err := ParseSID(input)
if err == nil {
t.Errorf("ParseSID(%#v): expected error, got none", input)
}
}
}
func TestMysql56GTIDString(t *testing.T) {
input := Mysql56GTID{
Server: SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
Sequence: 12345,
}
want := "00010203-0405-0607-0809-0a0b0c0d0e0f:12345"
if got := strings.ToLower(input.String()); got != want {
t.Errorf("%#v.String() = %#v, want %#v", input, got, want)
}
}
func TestMysql56GTIDFlavor(t *testing.T) {
input := Mysql56GTID{}
if got, want := input.Flavor(), "MySQL56"; got != want {
t.Errorf("%#v.Flavor() = %#v, want %#v", input, got, want)
}
}
func TestMysql56SequenceDomain(t *testing.T) {
input := Mysql56GTID{}
if got, want := input.SequenceDomain(), interface{}(nil); got != want {
t.Errorf("%#v.SequenceDomain() = %#v, want %#v", input, got, want)
}
}
func TestMysql56SourceServer(t *testing.T) {
input := Mysql56GTID{
Server: SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
}
want := interface{}(SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
if got := input.SourceServer(); got != want {
t.Errorf("%#v.SourceServer() = %#v, want %#v", input, got, want)
}
}
func TestMysql56SequenceNumber(t *testing.T) {
input := Mysql56GTID{
Server: SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
Sequence: 5432,
}
want := interface{}(int64(5432))
if got := input.SequenceNumber(); got != want {
t.Errorf("%#v.SequenceNumber() = %#v, want %#v", input, got, want)
}
}
func TestMysql56GTIDGTIDSet(t *testing.T) {
sid1 := SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
input := Mysql56GTID{Server: sid1, Sequence: 5432}
want := Mysql56GTIDSet{sid1: []interval{{5432, 5432}}}
if got := input.GTIDSet(); !got.Equal(want) {
t.Errorf("%#v.GTIDSet() = %#v, want %#v", input, got, want)
}
}

Просмотреть файл

@ -126,6 +126,9 @@ class MySQL56(MysqlFlavor):
def bootstrap_archive(self):
return "mysql-db-dir_5.6.24.tbz"
def extra_my_cnf(self):
return environment.vttop + "/config/mycnf/master_mysql56.cnf"
__mysql_flavor = None