diff --git a/mysql/mysql_gtid.go b/mysql/mysql_gtid.go index 522348f76..2d69a02aa 100644 --- a/mysql/mysql_gtid.go +++ b/mysql/mysql_gtid.go @@ -207,31 +207,61 @@ type UUIDSet struct { Intervals IntervalSlice } -func ParseUUIDSet(str string) (*UUIDSet, error) { - str = strings.TrimSpace(str) - sep := strings.Split(str, ":") - if len(sep) < 2 { - return nil, errors.Errorf("invalid GTID format, must UUID:interval[:interval]") - } - - var err error - s := new(UUIDSet) - if s.SID, err = uuid.Parse(sep[0]); err != nil { - return nil, errors.Trace(err) - } - - // Handle interval - for i := 1; i < len(sep); i++ { - if in, err := parseInterval(sep[i]); err != nil { - return nil, errors.Trace(err) - } else { - s.Intervals = append(s.Intervals, in) - } - } - - s.Intervals = s.Intervals.Normalize() - - return s, nil +// ParseUUIDSet parses a GTID set string into a map of UUIDSet structs keyed by their SID. +// Supports multi-UUID sets like "uuid1:1-10,uuid2:5-15". +func ParseUUIDSet(s string) (map[string]*UUIDSet, error) { + if s == "" { + return nil, nil + } + + uuidSets := strings.Split(strings.TrimSpace(s), ",") + if len(uuidSets) == 0 { + return nil, fmt.Errorf("empty UUID set") + } + + result := make(map[string]*UUIDSet) + for _, set := range uuidSets { + set = strings.TrimSpace(set) + if set == "" { + continue + } + + parts := strings.SplitN(set, ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid UUID set format: %s", set) + } + + sid, err := uuid.Parse(parts[0]) + if err != nil { + return nil, fmt.Errorf("invalid UUID: %s", parts[0]) + } + + // Check if this SID already exists in the map + uuidSet, exists := result[sid.String()] + if !exists { + uuidSet = &UUIDSet{SID: sid} + result[sid.String()] = uuidSet + } + + intervals := strings.Split(parts[1], ":") + for _, intervalStr := range intervals { + interval, err := parseInterval(intervalStr) + if err != nil { + return nil, fmt.Errorf("invalid interval in UUID set %s: %v", set, err) + } + uuidSet.Intervals = append(uuidSet.Intervals, interval) + } + + if len(uuidSet.Intervals) == 0 { + return nil, fmt.Errorf("no valid intervals in UUID set: %s", set) + } + uuidSet.Intervals = uuidSet.Intervals.Normalize() // Normalize intervals after adding + } + + if len(result) == 0 { + return nil, fmt.Errorf("no valid UUID sets parsed from: %s", s) + } + return result, nil } func NewUUIDSet(sid uuid.UUID, in ...Interval) *UUIDSet { @@ -398,23 +428,20 @@ type MysqlGTIDSet struct { var _ GTIDSet = &MysqlGTIDSet{} func ParseMysqlGTIDSet(str string) (GTIDSet, error) { - s := new(MysqlGTIDSet) - s.Sets = make(map[string]*UUIDSet) - if str == "" { - return s, nil - } - - sp := strings.Split(str, ",") - - // todo, handle redundant same uuid - for i := 0; i < len(sp); i++ { - if set, err := ParseUUIDSet(sp[i]); err != nil { - return nil, errors.Trace(err) - } else { - s.AddSet(set) - } - } - return s, nil + s := new(MysqlGTIDSet) + s.Sets = make(map[string]*UUIDSet) + if str == "" { + return s, nil + } + + sets, err := ParseUUIDSet(str) // Use the updated ParseUUIDSet + if err != nil { + return nil, errors.Trace(err) + } + for sid, set := range sets { + s.Sets[sid] = set + } + return s, nil } func DecodeMysqlGTIDSet(data []byte) (*MysqlGTIDSet, error) { diff --git a/mysql/mysql_gtid_test.go b/mysql/mysql_gtid_test.go new file mode 100644 index 000000000..2139a8802 --- /dev/null +++ b/mysql/mysql_gtid_test.go @@ -0,0 +1,91 @@ +package mysql + +import ( + "reflect" + "testing" + + "github.com/google/uuid" +) + +func TestParseUUIDSet(t *testing.T) { + tests := []struct { + input string + expected map[string]*UUIDSet + wantErr bool + }{ + { + input: "0b8beec9-911e-11e9-9f7b-8a057645f3f6:1-1175877800", + expected: map[string]*UUIDSet{ + "0b8beec9-911e-11e9-9f7b-8a057645f3f6": { + SID: uuid.Must(uuid.Parse("0b8beec9-911e-11e9-9f7b-8a057645f3f6")), + Intervals: []Interval{{Start: 1, Stop: 1175877801}}, // Stop is Start+1 for single intervals + }, + }, + wantErr: false, + }, + { + input: "0b8beec9-911e-11e9-9f7b-8a057645f3f6:1-1175877800,246e88bd-0288-11e8-9cee-230cd2fc765b:1-592884032", + expected: map[string]*UUIDSet{ + "0b8beec9-911e-11e9-9f7b-8a057645f3f6": { + SID: uuid.Must(uuid.Parse("0b8beec9-911e-11e9-9f7b-8a057645f3f6")), + Intervals: []Interval{{Start: 1, Stop: 1175877801}}, + }, + "246e88bd-0288-11e8-9cee-230cd2fc765b": { + SID: uuid.Must(uuid.Parse("246e88bd-0288-11e8-9cee-230cd2fc765b")), + Intervals: []Interval{{Start: 1, Stop: 592884033}}, + }, + }, + wantErr: false, + }, + { + input: "invalid", + wantErr: true, + }, + { + input: "", + expected: nil, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got, err := ParseUUIDSet(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseUUIDSet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("ParseUUIDSet() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestParseMysqlGTIDSet(t *testing.T) { + input := "0b8beec9-911e-11e9-9f7b-8a057645f3f6:1-1175877800,246e88bd-0288-11e8-9cee-230cd2fc765b:1-592884032" + expected := &MysqlGTIDSet{ + Sets: map[string]*UUIDSet{ + "0b8beec9-911e-11e9-9f7b-8a057645f3f6": { + SID: uuid.Must(uuid.Parse("0b8beec9-911e-11e9-9f7b-8a057645f3f6")), + Intervals: []Interval{{Start: 1, Stop: 1175877801}}, + }, + "246e88bd-0288-11e8-9cee-230cd2fc765b": { + SID: uuid.Must(uuid.Parse("246e88bd-0288-11e8-9cee-230cd2fc765b")), + Intervals: []Interval{{Start: 1, Stop: 592884033}}, + }, + }, + } + + got, err := ParseMysqlGTIDSet(input) + if err != nil { + t.Fatalf("ParseMysqlGTIDSet() error = %v", err) + } + if !reflect.DeepEqual(got, expected) { + t.Errorf("ParseMysqlGTIDSet() = %v, want %v", got, expected) + } + + if got.String() != input { + t.Errorf("String() = %v, want %v", got.String(), input) + } +} \ No newline at end of file diff --git a/mysql/mysql_test.go b/mysql/mysql_test.go index 4486c2b9b..efd9f03e2 100644 --- a/mysql/mysql_test.go +++ b/mysql/mysql_test.go @@ -1,362 +1,428 @@ package mysql import ( - "fmt" - "strings" - "testing" + "fmt" + "reflect" + "strings" + "testing" - "github.com/google/uuid" - "github.com/stretchr/testify/require" + "github.com/google/uuid" + "github.com/stretchr/testify/require" - _ "github.com/go-mysql-org/go-mysql/test_util" // Will register common flags + _ "github.com/go-mysql-org/go-mysql/test_util" // Will register common flags ) func TestMysqlGTIDInterval(t *testing.T) { - i, err := parseInterval("1-2") - require.NoError(t, err) - require.Equal(t, Interval{1, 3}, i) + i, err := parseInterval("1-2") + require.NoError(t, err) + require.Equal(t, Interval{1, 3}, i) - i, err = parseInterval("1") - require.NoError(t, err) - require.Equal(t, Interval{1, 2}, i) + i, err = parseInterval("1") + require.NoError(t, err) + require.Equal(t, Interval{1, 2}, i) - i, err = parseInterval("1-1") - require.NoError(t, err) - require.Equal(t, Interval{1, 2}, i) + i, err = parseInterval("1-1") + require.NoError(t, err) + require.Equal(t, Interval{1, 2}, i) } func TestMysqlGTIDIntervalSlice(t *testing.T) { - i := IntervalSlice{Interval{1, 2}, Interval{2, 4}, Interval{2, 3}} - i.Sort() - require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{2, 3}, Interval{2, 4}}, i) - n := i.Normalize() - require.Equal(t, IntervalSlice{Interval{1, 4}}, n) - - i = IntervalSlice{Interval{1, 2}, Interval{3, 5}, Interval{1, 3}} - i.Sort() - require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{1, 3}, Interval{3, 5}}, i) - n = i.Normalize() - require.Equal(t, IntervalSlice{Interval{1, 5}}, n) - - i = IntervalSlice{Interval{1, 2}, Interval{4, 5}, Interval{1, 3}} - i.Sort() - require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{1, 3}, Interval{4, 5}}, i) - n = i.Normalize() - require.Equal(t, IntervalSlice{Interval{1, 3}, Interval{4, 5}}, n) - - i = IntervalSlice{Interval{1, 4}, Interval{2, 3}} - i.Sort() - require.Equal(t, IntervalSlice{Interval{1, 4}, Interval{2, 3}}, i) - n = i.Normalize() - require.Equal(t, IntervalSlice{Interval{1, 4}}, n) - - n1 := IntervalSlice{Interval{1, 3}, Interval{4, 5}} - n2 := IntervalSlice{Interval{1, 2}} - - require.True(t, n1.Contain(n2)) - require.False(t, n2.Contain(n1)) - - n1 = IntervalSlice{Interval{1, 3}, Interval{4, 5}} - n2 = IntervalSlice{Interval{1, 6}} - - require.False(t, n1.Contain(n2)) - require.True(t, n2.Contain(n1)) + i := IntervalSlice{Interval{1, 2}, Interval{2, 4}, Interval{2, 3}} + i.Sort() + require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{2, 3}, Interval{2, 4}}, i) + n := i.Normalize() + require.Equal(t, IntervalSlice{Interval{1, 4}}, n) + + i = IntervalSlice{Interval{1, 2}, Interval{3, 5}, Interval{1, 3}} + i.Sort() + require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{1, 3}, Interval{3, 5}}, i) + n = i.Normalize() + require.Equal(t, IntervalSlice{Interval{1, 5}}, n) + + i = IntervalSlice{Interval{1, 2}, Interval{4, 5}, Interval{1, 3}} + i.Sort() + require.Equal(t, IntervalSlice{Interval{1, 2}, Interval{1, 3}, Interval{4, 5}}, i) + n = i.Normalize() + require.Equal(t, IntervalSlice{Interval{1, 3}, Interval{4, 5}}, n) + + i = IntervalSlice{Interval{1, 4}, Interval{2, 3}} + i.Sort() + require.Equal(t, IntervalSlice{Interval{1, 4}, Interval{2, 3}}, i) + n = i.Normalize() + require.Equal(t, IntervalSlice{Interval{1, 4}}, n) + + n1 := IntervalSlice{Interval{1, 3}, Interval{4, 5}} + n2 := IntervalSlice{Interval{1, 2}} + + require.True(t, n1.Contain(n2)) + require.False(t, n2.Contain(n1)) + + n1 = IntervalSlice{Interval{1, 3}, Interval{4, 5}} + n2 = IntervalSlice{Interval{1, 6}} + + require.False(t, n1.Contain(n2)) + require.True(t, n2.Contain(n1)) } func TestMysqlGTIDInsertInterval(t *testing.T) { - i := IntervalSlice{Interval{100, 200}} - i.InsertInterval(Interval{300, 400}) - require.Equal(t, IntervalSlice{Interval{100, 200}, Interval{300, 400}}, i) + i := IntervalSlice{Interval{100, 200}} + i.InsertInterval(Interval{300, 400}) + require.Equal(t, IntervalSlice{Interval{100, 200}, Interval{300, 400}}, i) - i.InsertInterval(Interval{50, 70}) - require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{100, 200}, Interval{300, 400}}, i) + i.InsertInterval(Interval{50, 70}) + require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{100, 200}, Interval{300, 400}}, i) - i.InsertInterval(Interval{101, 201}) - require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{100, 201}, Interval{300, 400}}, i) + i.InsertInterval(Interval{101, 201}) + require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{100, 201}, Interval{300, 400}}, i) - i.InsertInterval(Interval{99, 202}) - require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 202}, Interval{300, 400}}, i) + i.InsertInterval(Interval{99, 202}) + require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 202}, Interval{300, 400}}, i) - i.InsertInterval(Interval{102, 302}) - require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 400}}, i) + i.InsertInterval(Interval{102, 302}) + require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 400}}, i) - i.InsertInterval(Interval{500, 600}) - require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 400}, Interval{500, 600}}, i) + i.InsertInterval(Interval{500, 600}) + require.Equal(t, IntervalSlice{Interval{50, 70}, Interval{99, 400}, Interval{500, 600}}, i) - i.InsertInterval(Interval{50, 100}) - require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}}, i) + i.InsertInterval(Interval{50, 100}) + require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}}, i) - i.InsertInterval(Interval{900, 1000}) - require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}, Interval{900, 1000}}, i) + i.InsertInterval(Interval{900, 1000}) + require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}, Interval{900, 1000}}, i) - i.InsertInterval(Interval{1010, 1020}) - require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}, Interval{900, 1000}, Interval{1010, 1020}}, i) + i.InsertInterval(Interval{1010, 1020}) + require.Equal(t, IntervalSlice{Interval{50, 400}, Interval{500, 600}, Interval{900, 1000}, Interval{1010, 1020}}, i) - i.InsertInterval(Interval{49, 1000}) - require.Equal(t, IntervalSlice{Interval{49, 1000}, Interval{1010, 1020}}, i) + i.InsertInterval(Interval{49, 1000}) + require.Equal(t, IntervalSlice{Interval{49, 1000}, Interval{1010, 1020}}, i) - i.InsertInterval(Interval{1, 1012}) - require.Equal(t, IntervalSlice{Interval{1, 1020}}, i) + i.InsertInterval(Interval{1, 1012}) + require.Equal(t, IntervalSlice{Interval{1, 1020}}, i) } func TestMysqlGTIDCodec(t *testing.T) { - us, err := ParseUUIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2") - require.NoError(t, err) + us, err := ParseUUIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2") + require.NoError(t, err) + require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", us.String()) - require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", us.String()) + buf := us.Encode() + err = us.Decode(buf) + require.NoError(t, err) - buf := us.Encode() - err = us.Decode(buf) - require.NoError(t, err) + gs, err := ParseMysqlGTIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2,de278ad0-2106-11e4-9f8e-6edd0ca20948:1-2") + require.NoError(t, err) - gs, err := ParseMysqlGTIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2,de278ad0-2106-11e4-9f8e-6edd0ca20948:1-2") - require.NoError(t, err) - - buf = gs.Encode() - o, err := DecodeMysqlGTIDSet(buf) - require.NoError(t, err) - require.Equal(t, gs, o) + buf = gs.Encode() + o, err := DecodeMysqlGTIDSet(buf) + require.NoError(t, err) + require.Equal(t, gs, o) } func TestMysqlUpdate(t *testing.T) { - g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") - require.NoError(t, err) - - err = g1.Update("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58") - require.NoError(t, err) - - require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58", strings.ToUpper(g1.String())) - - g1, err = ParseMysqlGTIDSet(` - 519CE70F-A893-11E9-A95A-B32DC65A7026:1-1154661, - 5C9CA52B-9F11-11E9-8EAF-3381EC1CC790:1-244, - 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1-1221371, - F2B50559-A891-11E9-B646-884FF0CA2043:1-479261 - `) - require.NoError(t, err) - - err = g1.Update(` - 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1221110-1221371, - F2B50559-A891-11E9-B646-884FF0CA2043:478509-479266 - `) - require.NoError(t, err) - - g2, err := ParseMysqlGTIDSet(` - 519CE70F-A893-11E9-A95A-B32DC65A7026:1-1154661, - 5C9CA52B-9F11-11E9-8EAF-3381EC1CC790:1-244, - 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1-1221371, - F2B50559-A891-11E9-B646-884FF0CA2043:1-479266 - `) - require.NoError(t, err) - require.True(t, g1.Equal(g2)) + g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") + require.NoError(t, err) + + err = g1.Update("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58") + require.NoError(t, err) + require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58", strings.ToUpper(g1.String())) + + g1, err = ParseMysqlGTIDSet(` + 519CE70F-A893-11E9-A95A-B32DC65A7026:1-1154661, + 5C9CA52B-9F11-11E9-8EAF-3381EC1CC790:1-244, + 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1-1221371, + F2B50559-A891-11E9-B646-884FF0CA2043:1-479261 + `) + require.NoError(t, err) + + err = g1.Update(` + 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1221110-1221371, + F2B50559-A891-11E9-B646-884FF0CA2043:478509-479266 + `) + require.NoError(t, err) + + g2, err := ParseMysqlGTIDSet(` + 519CE70F-A893-11E9-A95A-B32DC65A7026:1-1154661, + 5C9CA52B-9F11-11E9-8EAF-3381EC1CC790:1-244, + 802D69FD-A3B6-11E9-B1EA-50BAB55BA838:1-1221371, + F2B50559-A891-11E9-B646-884FF0CA2043:1-479266 + `) + require.NoError(t, err) + require.True(t, g1.Equal(g2)) } func TestMysqlAddGTID(t *testing.T) { - g, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") - require.NoError(t, err) + g, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") + require.NoError(t, err) - g1 := g.(*MysqlGTIDSet) + g1 := g.(*MysqlGTIDSet) - u, err := uuid.Parse("3E11FA47-71CA-11E1-9E33-C80AA9429562") - require.NoError(t, err) + u, err := uuid.Parse("3E11FA47-71CA-11E1-9E33-C80AA9429562") + require.NoError(t, err) - g1.AddGTID(u, 58) - require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58", strings.ToUpper(g1.String())) + g1.AddGTID(u, 58) + require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58", strings.ToUpper(g1.String())) - g1.AddGTID(u, 60) - require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58:60", strings.ToUpper(g1.String())) + g1.AddGTID(u, 60) + require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-58:60", strings.ToUpper(g1.String())) - g1.AddGTID(u, 59) - require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-60", strings.ToUpper(g1.String())) + g1.AddGTID(u, 59) + require.Equal(t, "3E11FA47-71CA-11E1-9E33-C80AA9429562:21-60", strings.ToUpper(g1.String())) - u2, err := uuid.Parse("519CE70F-A893-11E9-A95A-B32DC65A7026") - require.NoError(t, err) - g1.AddGTID(u2, 58) - g2, err := ParseMysqlGTIDSet(` - 3E11FA47-71CA-11E1-9E33-C80AA9429562:21-60, - 519CE70F-A893-11E9-A95A-B32DC65A7026:58 + u2, err := uuid.Parse("519CE70F-A893-11E9-A95A-B32DC65A7026") + require.NoError(t, err) + g1.AddGTID(u2, 58) + g2, err := ParseMysqlGTIDSet(` + 3E11FA47-71CA-11E1-9E33-C80AA9429562:21-60, + 519CE70F-A893-11E9-A95A-B32DC65A7026:58 `) - require.NoError(t, err) - require.True(t, g2.Equal(g1)) + require.NoError(t, err) + require.True(t, g2.Equal(g1)) } func TestMysqlGTIDContain(t *testing.T) { - g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:23") - require.NoError(t, err) + g1, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:23") + require.NoError(t, err) - g2, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") - require.NoError(t, err) + g2, err := ParseMysqlGTIDSet("3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57") + require.NoError(t, err) - require.True(t, g2.Contain(g1)) - require.False(t, g1.Contain(g2)) + require.True(t, g2.Contain(g1)) + require.False(t, g1.Contain(g2)) } func TestMysqlGTIDAdd(t *testing.T) { - testCases := []struct { - left, right, expected string - }{ - // simple cases works: - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23:28-57"}, - // summ is associative operation - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23:28-57"}, - // merge intervals: - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23-27", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23-57"}, - } - - for _, tc := range testCases { - m1 := mysqlGTIDfromString(t, tc.left) - m2 := mysqlGTIDfromString(t, tc.right) - err := m1.Add(m2) - require.NoError(t, err) - one := fmt.Sprintf("%s + %s = %s", tc.left, tc.right, strings.ToUpper(m1.String())) - other := fmt.Sprintf("%s + %s = %s", tc.left, tc.right, tc.expected) - require.Equal(t, other, one) - } + testCases := []struct { + left, right, expected string + }{ + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23:28-57"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23:28-57"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23-27", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23-57"}, + } + + for _, tc := range testCases { + m1 := mysqlGTIDfromString(t, tc.left) + m2 := mysqlGTIDfromString(t, tc.right) + err := m1.Add(m2) + require.NoError(t, err) + one := fmt.Sprintf("%s + %s = %s", tc.left, tc.right, strings.ToUpper(m1.String())) + other := fmt.Sprintf("%s + %s = %s", tc.left, tc.right, tc.expected) + require.Equal(t, other, one) + } } func TestMysqlGTIDMinus(t *testing.T) { - testCases := []struct { - left, right, expected string - }{ - // Minuses that doesn't affect original value: - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57"}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:1-22:24-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "ABCDEF12-1234-5678-9012-345678901234:1-1000", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, - // Minuses that change original value: - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-57:60-90", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20-22:24-57:60-90"}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-57:60-90", "3E11FA47-71CA-11E1-9E33-C80AA9429562:22-70", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20-21:71-90"}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", ""}, - {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-21", "3E11FA47-71CA-11E1-9E33-C80AA9429562:21", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20"}, - {"582A11ED-786C-11EC-ACCC-E0356662B76E:1-209692", "582A11ED-786C-11EC-ACCC-E0356662B76E:1-146519", "582A11ED-786C-11EC-ACCC-E0356662B76E:146520-209692"}, - {"582A11ED-786C-11EC-ACCC-E0356662B76E:1-209692", "582A11ED-786C-11EC-ACCC-E0356662B76E:2-146519", "582A11ED-786C-11EC-ACCC-E0356662B76E:1:146520-209692"}, - } - - for _, tc := range testCases { - m1 := mysqlGTIDfromString(t, tc.left) - m2 := mysqlGTIDfromString(t, tc.right) - err := m1.Minus(m2) - require.NoError(t, err) - one := fmt.Sprintf("%s - %s = %s", tc.left, tc.right, strings.ToUpper(m1.String())) - other := fmt.Sprintf("%s - %s = %s", tc.left, tc.right, tc.expected) - require.Equal(t, other, one) - } + testCases := []struct { + left, right, expected string + }{ + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:1-22:24-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "ABCDEF12-1234-5678-9012-345678901234:1-1000", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-57:60-90", "3E11FA47-71CA-11E1-9E33-C80AA9429562:23", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20-22:24-57:60-90"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-57:60-90", "3E11FA47-71CA-11E1-9E33-C80AA9429562:22-70", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20-21:71-90"}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", "3E11FA47-71CA-11E1-9E33-C80AA9429562:28-57", ""}, + {"3E11FA47-71CA-11E1-9E33-C80AA9429562:20-21", "3E11FA47-71CA-11E1-9E33-C80AA9429562:21", "3E11FA47-71CA-11E1-9E33-C80AA9429562:20"}, + {"582A11ED-786C-11EC-ACCC-E0356662B76E:1-209692", "582A11ED-786C-11EC-ACCC-E0356662B76E:1-146519", "582A11ED-786C-11EC-ACCC-E0356662B76E:146520-209692"}, + {"582A11ED-786C-11EC-ACCC-E0356662B76E:1-209692", "582A11ED-786C-11EC-ACCC-E0356662B76E:2-146519", "582A11ED-786C-11EC-ACCC-E0356662B76E:1:146520-209692"}, + } + + for _, tc := range testCases { + m1 := mysqlGTIDfromString(t, tc.left) + m2 := mysqlGTIDfromString(t, tc.right) + err := m1.Minus(m2) + require.NoError(t, err) + one := fmt.Sprintf("%s - %s = %s", tc.left, tc.right, strings.ToUpper(m1.String())) + other := fmt.Sprintf("%s - %s = %s", tc.left, tc.right, tc.expected) + require.Equal(t, other, one) + } } func TestMysqlParseBinaryInt8(t *testing.T) { - i8 := ParseBinaryInt8([]byte{128}) - require.Equal(t, int8(-128), i8) + i8 := ParseBinaryInt8([]byte{128}) + require.Equal(t, int8(-128), i8) } func TestMysqlParseBinaryUint8(t *testing.T) { - u8 := ParseBinaryUint8([]byte{128}) - require.Equal(t, uint8(128), u8) + u8 := ParseBinaryUint8([]byte{128}) + require.Equal(t, uint8(128), u8) } func TestMysqlParseBinaryInt16(t *testing.T) { - i16 := ParseBinaryInt16([]byte{1, 128}) - require.Equal(t, int16(-128*256+1), i16) + i16 := ParseBinaryInt16([]byte{1, 128}) + require.Equal(t, int16(-128*256+1), i16) } func TestMysqlParseBinaryUint16(t *testing.T) { - u16 := ParseBinaryUint16([]byte{1, 128}) - require.Equal(t, uint16(128*256+1), u16) + u16 := ParseBinaryUint16([]byte{1, 128}) + require.Equal(t, uint16(128*256+1), u16) } func TestMysqlParseBinaryInt24(t *testing.T) { - i32 := ParseBinaryInt24([]byte{1, 2, 128}) - require.Equal(t, int32(-128*65536+2*256+1), i32) + i32 := ParseBinaryInt24([]byte{1, 2, 128}) + require.Equal(t, int32(-128*65536+2*256+1), i32) } func TestMysqlParseBinaryUint24(t *testing.T) { - u32 := ParseBinaryUint24([]byte{1, 2, 128}) - require.Equal(t, uint32(128*65536+2*256+1), u32) + u32 := ParseBinaryUint24([]byte{1, 2, 128}) + require.Equal(t, uint32(128*65536+2*256+1), u32) } func TestMysqlParseBinaryInt32(t *testing.T) { - i32 := ParseBinaryInt32([]byte{1, 2, 3, 128}) - require.Equal(t, int32(-128*16777216+3*65536+2*256+1), i32) + i32 := ParseBinaryInt32([]byte{1, 2, 3, 128}) + require.Equal(t, int32(-128*16777216+3*65536+2*256+1), i32) } func TestMysqlParseBinaryUint32(t *testing.T) { - u32 := ParseBinaryUint32([]byte{1, 2, 3, 128}) - require.Equal(t, uint32(128*16777216+3*65536+2*256+1), u32) + u32 := ParseBinaryUint32([]byte{1, 2, 3, 128}) + require.Equal(t, uint32(128*16777216+3*65536+2*256+1), u32) } func TestMysqlParseBinaryInt64(t *testing.T) { - i64 := ParseBinaryInt64([]byte{1, 2, 3, 4, 5, 6, 7, 128}) - require.Equal(t, -128*int64(72057594037927936)+7*int64(281474976710656)+6*int64(1099511627776)+5*int64(4294967296)+4*16777216+3*65536+2*256+1, i64) + i64 := ParseBinaryInt64([]byte{1, 2, 3, 4, 5, 6, 7, 128}) + require.Equal(t, -128*int64(72057594037927936)+7*int64(281474976710656)+6*int64(1099511627776)+5*int64(4294967296)+4*16777216+3*65536+2*256+1, i64) } func TestMysqlParseBinaryUint64(t *testing.T) { - u64 := ParseBinaryUint64([]byte{1, 2, 3, 4, 5, 6, 7, 128}) - require.Equal(t, 128*uint64(72057594037927936)+7*uint64(281474976710656)+6*uint64(1099511627776)+5*uint64(4294967296)+4*16777216+3*65536+2*256+1, u64) + u64 := ParseBinaryUint64([]byte{1, 2, 3, 4, 5, 6, 7, 128}) + require.Equal(t, 128*uint64(72057594037927936)+7*uint64(281474976710656)+6*uint64(1099511627776)+5*uint64(4294967296)+4*16777216+3*65536+2*256+1, u64) } func TestErrorCode(t *testing.T) { - tbls := []struct { - msg string - code int - }{ - {"ERROR 1094 (HY000): Unknown thread id: 1094", 1094}, - {"error string", 0}, - {"abcdefg", 0}, - {"123455 ks094", 0}, - {"ERROR 1046 (3D000): Unknown error 1046", 1046}, - } - for _, v := range tbls { - require.Equal(t, v.code, ErrorCode(v.msg)) - } + tbls := []struct { + msg string + code int + }{ + {"ERROR 1094 (HY000): Unknown thread id: 1094", 1094}, + {"error string", 0}, + {"abcdefg", 0}, + {"123455 ks094", 0}, + {"ERROR 1046 (3D000): Unknown error 1046", 1046}, + } + for _, v := range tbls { + require.Equal(t, v.code, ErrorCode(v.msg)) + } } func TestMysqlNullDecode(t *testing.T) { - _, isNull, n := LengthEncodedInt([]byte{0xfb}) - - require.True(t, isNull) - require.Equal(t, 1, n) + _, isNull, n := LengthEncodedInt([]byte{0xfb}) + require.True(t, isNull) + require.Equal(t, 1, n) } func TestMysqlUUIDClone(t *testing.T) { - us, err := ParseUUIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2") - require.NoError(t, err) - require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", us.String()) + us, err := ParseUUIDSet("de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2") + require.NoError(t, err) + require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", us.String()) - clone := us.Clone() - require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", clone.String()) + clone := us.Clone() + require.Equal(t, "de278ad0-2106-11e4-9f8e-6edd0ca20947:1-2", clone.String()) } func TestMysqlEmptyDecode(t *testing.T) { - _, isNull, n := LengthEncodedInt(nil) - require.True(t, isNull) - require.Equal(t, 0, n) + _, isNull, n := LengthEncodedInt(nil) + require.True(t, isNull) + require.Equal(t, 0, n) } func mysqlGTIDfromString(t *testing.T, gtidStr string) MysqlGTIDSet { - gtid, err := ParseMysqlGTIDSet(gtidStr) - require.NoError(t, err) - - return *gtid.(*MysqlGTIDSet) + gtid, err := ParseMysqlGTIDSet(gtidStr) + require.NoError(t, err) + return *gtid.(*MysqlGTIDSet) } func TestValidateFlavor(t *testing.T) { - tbls := []struct { - flavor string - valid bool - }{ - {"mysql", true}, - {"mariadb", true}, - {"maria", false}, - {"MariaDB", true}, - {"msql", false}, - {"mArIAdb", true}, - } - - for _, f := range tbls { - err := ValidateFlavor(f.flavor) - if f.valid == true { - require.NoError(t, err) - } else { - require.Error(t, err) - } - } + tbls := []struct { + flavor string + valid bool + }{ + {"mysql", true}, + {"mariadb", true}, + {"maria", false}, + {"MariaDB", true}, + {"msql", false}, + {"mArIAdb", true}, + } + for _, f := range tbls { + err := ValidateFlavor(f.flavor) + if f.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } } + +func TestParseUUIDSet(t *testing.T) { + tests := []struct { + input string + expected *UUIDSet + wantErr bool + }{ + { + input: "0b8beec9-911e-11e9-9f7b-8a057645f3f6:1-1175877800", + expected: &UUIDSet{ + SID: uuid.Must(uuid.Parse("0b8beec9-911e-11e9-9f7b-8a057645f3f6")), + Intervals: []Interval{{Start: 1, Stop: 1175877801}}, + }, + wantErr: false, + }, + { + input: "246e88bd-0288-11e8-9cee-230cd2fc765b:1-592884032", + expected: &UUIDSet{ + SID: uuid.Must(uuid.Parse("246e88bd-0288-11e8-9cee-230cd2fc765b")), + Intervals: []Interval{{Start: 1, Stop: 592884033}}, + }, + wantErr: false, + }, + { + input: "invalid", + wantErr: true, + }, + { + input: "", + expected: nil, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got, err := ParseUUIDSet(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ParseUUIDSet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("ParseUUIDSet() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestParseMysqlGTIDSet(t *testing.T) { + input := "0b8beec9-911e-11e9-9f7b-8a057645f3f6:1-1175877800,246e88bd-0288-11e8-9cee-230cd2fc765b:1-592884032" + expected := &MysqlGTIDSet{ + Sets: map[string]*UUIDSet{ + "0b8beec9-911e-11e9-9f7b-8a057645f3f6": { + SID: uuid.Must(uuid.Parse("0b8beec9-911e-11e9-9f7b-8a057645f3f6")), + Intervals: []Interval{{Start: 1, Stop: 1175877801}}, + }, + "246e88bd-0288-11e8-9cee-230cd2fc765b": { + SID: uuid.Must(uuid.Parse("246e88bd-0288-11e8-9cee-230cd2fc765b")), + Intervals: []Interval{{Start: 1, Stop: 592884033}}, + }, + }, + } + + got, err := ParseMysqlGTIDSet(input) + if err != nil { + t.Fatalf("ParseMysqlGTIDSet() error = %v", err) + } + if !reflect.DeepEqual(got, expected) { + t.Errorf("ParseMysqlGTIDSet() = %v, want %v", got, expected) + } + + if got.String() != input { + t.Errorf("String() = %v, want %v", got.String(), input) + } +} \ No newline at end of file