From 1cd37f7ca07e8bef8e0d1ca6dbca78bb34caa76a Mon Sep 17 00:00:00 2001 From: Anthony Yeh Date: Fri, 1 May 2015 17:27:39 -0700 Subject: [PATCH] Implement GTID and GTIDSet for MySQL 5.6. --- go/vt/mysqlctl/proto/mysql56_gtid.go | 85 +++++ go/vt/mysqlctl/proto/mysql56_gtid_set.go | 251 ++++++++++++++ go/vt/mysqlctl/proto/mysql56_gtid_set_test.go | 321 ++++++++++++++++++ go/vt/mysqlctl/proto/mysql56_gtid_test.go | 103 ++++++ 4 files changed, 760 insertions(+) create mode 100644 go/vt/mysqlctl/proto/mysql56_gtid.go create mode 100644 go/vt/mysqlctl/proto/mysql56_gtid_set.go create mode 100644 go/vt/mysqlctl/proto/mysql56_gtid_set_test.go create mode 100644 go/vt/mysqlctl/proto/mysql56_gtid_test.go diff --git a/go/vt/mysqlctl/proto/mysql56_gtid.go b/go/vt/mysqlctl/proto/mysql56_gtid.go new file mode 100644 index 0000000000..19bf35efa2 --- /dev/null +++ b/go/vt/mysqlctl/proto/mysql56_gtid.go @@ -0,0 +1,85 @@ +// 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" +) + +const mysql56FlavorID = "MySQL56" + +// 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) +} diff --git a/go/vt/mysqlctl/proto/mysql56_gtid_set.go b/go/vt/mysqlctl/proto/mysql56_gtid_set.go new file mode 100644 index 0000000000..7ed2d739b0 --- /dev/null +++ b/go/vt/mysqlctl/proto/mysql56_gtid_set.go @@ -0,0 +1,251 @@ +// 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" + "sort" + "strconv" +) + +type interval struct { + start, end int64 +} + +func (iv interval) contains(other interval) bool { + return iv.start <= other.start && other.end <= iv.end +} + +// 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 +} + +// Len implements sort.Interface. +type sidList []SID + +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() +} diff --git a/go/vt/mysqlctl/proto/mysql56_gtid_set_test.go b/go/vt/mysqlctl/proto/mysql56_gtid_set_test.go new file mode 100644 index 0000000000..ee472f892b --- /dev/null +++ b/go/vt/mysqlctl/proto/mysql56_gtid_set_test.go @@ -0,0 +1,321 @@ +// 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" + "strings" + "testing" +) + +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("Equal(%#v) = false, want true", other) + } + } + + // 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("Equal(%#v) = true, want false", other) + } + } +} + +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) + } +} diff --git a/go/vt/mysqlctl/proto/mysql56_gtid_test.go b/go/vt/mysqlctl/proto/mysql56_gtid_test.go new file mode 100644 index 0000000000..656fd293c7 --- /dev/null +++ b/go/vt/mysqlctl/proto/mysql56_gtid_test.go @@ -0,0 +1,103 @@ +// 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 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) + } +}