From 1f65914378d708868c5ede7002ac17164a19b7ba Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 3 Mar 2021 14:34:25 +0800 Subject: [PATCH 1/5] mysql: use numeric comparison for binlog filename --- mysql/position.go | 36 +++++++++++++++++++++++++---------- mysql/position_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 mysql/position_test.go diff --git a/mysql/position.go b/mysql/position.go index bee5485d5..e696e77f3 100644 --- a/mysql/position.go +++ b/mysql/position.go @@ -2,6 +2,9 @@ package mysql import ( "fmt" + "path/filepath" + "strconv" + "strings" ) // For binlog filename + position based replication @@ -12,22 +15,35 @@ type Position struct { func (p Position) Compare(o Position) int { // First compare binlog name - if p.Name > o.Name { + nameCmp := CompareBinlogFileName(p.Name, o.Name) + if nameCmp != 0 { + return nameCmp + } + // Same binlog file, compare position + if p.Pos > o.Pos { return 1 - } else if p.Name < o.Name { + } else if p.Pos < o.Pos { return -1 } else { - // Same binlog file, compare position - if p.Pos > o.Pos { - return 1 - } else if p.Pos < o.Pos { - return -1 - } else { - return 0 - } + return 0 } } func (p Position) String() string { return fmt.Sprintf("(%s, %d)", p.Name, p.Pos) } + +func CompareBinlogFileName(a, b string) int { + // mysqld appends a numeric extension to the binary log base name to generate binary log file names + // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html + aNum, _ := strconv.Atoi(strings.TrimLeft(filepath.Ext(a)[1:], "0")) + bNum, _ := strconv.Atoi(strings.TrimLeft(filepath.Ext(b)[1:], "0")) + + if aNum > bNum { + return 1 + } else if aNum < bNum { + return -1 + } else { + return 0 + } +} diff --git a/mysql/position_test.go b/mysql/position_test.go new file mode 100644 index 000000000..6e2b23a33 --- /dev/null +++ b/mysql/position_test.go @@ -0,0 +1,43 @@ +package mysql + +import ( + "github.com/pingcap/check" +) + +type positionCompareSuite struct { +} + +var _ = check.Suite(&positionCompareSuite{}) + +func (t *positionCompareSuite) TestPosCompare(c *check.C) { + ascendingPositions := []Position{ + { + "mysql-bin.000001", + 4, + }, + { + "mysql-bin.000001", + 100, + }, + { + "mysql-bin.000002", + 4, + }, + { + "mysql-bin.999999", + 4, + }, + { + "mysql-bin.1000000", + 4, + }, + } + + for i := 1; i < len(ascendingPositions); i++ { + c.Assert(ascendingPositions[i-1].Compare(ascendingPositions[i]), check.Equals, -1) + } + + for _, p := range ascendingPositions { + c.Assert(p.Compare(p), check.Equals, 0) + } +} \ No newline at end of file From 3080e93421f9dbf80e40ea23d762c833e3ab923d Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 3 Mar 2021 15:15:32 +0800 Subject: [PATCH 2/5] support empty filename --- mysql/position.go | 11 +++++++++++ mysql/position_test.go | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/mysql/position.go b/mysql/position.go index e696e77f3..73e3e5afc 100644 --- a/mysql/position.go +++ b/mysql/position.go @@ -34,6 +34,17 @@ func (p Position) String() string { } func CompareBinlogFileName(a, b string) int { + // sometimes it's convenient to construct a `Position` literal with no `Name` + if a == "" && b == "" { + return 0 + } + if a == "" { + return -1 + } + if b == "" { + return 1 + } + // mysqld appends a numeric extension to the binary log base name to generate binary log file names // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html aNum, _ := strconv.Atoi(strings.TrimLeft(filepath.Ext(a)[1:], "0")) diff --git a/mysql/position_test.go b/mysql/position_test.go index 6e2b23a33..806749803 100644 --- a/mysql/position_test.go +++ b/mysql/position_test.go @@ -11,6 +11,14 @@ var _ = check.Suite(&positionCompareSuite{}) func (t *positionCompareSuite) TestPosCompare(c *check.C) { ascendingPositions := []Position{ + { + "", + 4, + }, + { + "", + 100, + }, { "mysql-bin.000001", 4, From dcbfeb256ea9fe66ab5257650d0fe3d65f716e6a Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 3 Mar 2021 17:53:37 +0800 Subject: [PATCH 3/5] keep compatibility --- mysql/position.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/mysql/position.go b/mysql/position.go index 73e3e5afc..54e6881a2 100644 --- a/mysql/position.go +++ b/mysql/position.go @@ -2,7 +2,6 @@ package mysql import ( "fmt" - "path/filepath" "strconv" "strings" ) @@ -37,22 +36,42 @@ func CompareBinlogFileName(a, b string) int { // sometimes it's convenient to construct a `Position` literal with no `Name` if a == "" && b == "" { return 0 - } - if a == "" { + } else if a == "" { return -1 - } - if b == "" { + } else if b == "" { return 1 } - // mysqld appends a numeric extension to the binary log base name to generate binary log file names - // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html - aNum, _ := strconv.Atoi(strings.TrimLeft(filepath.Ext(a)[1:], "0")) - bNum, _ := strconv.Atoi(strings.TrimLeft(filepath.Ext(b)[1:], "0")) + splitBinlogName := func(n string) (string, int) { + // mysqld appends a numeric extension to the binary log base name to generate binary log file names + // ... + // If you supply an extension in the log name (for example, --log-bin=base_name.extension), + // the extension is silently removed and ignored. + // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html + parts := strings.Split(n, ".") + if len(parts) != 2 { + panic(fmt.Sprintf("binlog file %s doesn't contain one dot", a)) + } + seq, err := strconv.Atoi(parts[1]) + if err != nil { + panic(fmt.Sprintf("binlog file %s doesn't contain numeric extension", err)) + } + return parts[0], seq + } + + aBase, aSeq := splitBinlogName(a) + bBase, bSeq := splitBinlogName(b) + + // try keeping backward compatibility + if aBase > bBase { + return 1 + } else if aBase < bBase { + return -1 + } - if aNum > bNum { + if aSeq > bSeq { return 1 - } else if aNum < bNum { + } else if aSeq < bSeq { return -1 } else { return 0 From b0e44c2c5623876d5d327e329fb6855a067e6c4e Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 3 Mar 2021 18:03:54 +0800 Subject: [PATCH 4/5] more compatibility --- mysql/position.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mysql/position.go b/mysql/position.go index 54e6881a2..c592d6363 100644 --- a/mysql/position.go +++ b/mysql/position.go @@ -48,21 +48,22 @@ func CompareBinlogFileName(a, b string) int { // If you supply an extension in the log name (for example, --log-bin=base_name.extension), // the extension is silently removed and ignored. // ref: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html - parts := strings.Split(n, ".") - if len(parts) != 2 { - panic(fmt.Sprintf("binlog file %s doesn't contain one dot", a)) + i := strings.LastIndexByte(n, '.') + if i == -1 { + // try keeping backward compatibility + return n, 0 } - seq, err := strconv.Atoi(parts[1]) + + seq, err := strconv.Atoi(n[i+1:]) if err != nil { panic(fmt.Sprintf("binlog file %s doesn't contain numeric extension", err)) } - return parts[0], seq + return n[:i], seq } aBase, aSeq := splitBinlogName(a) bBase, bSeq := splitBinlogName(b) - // try keeping backward compatibility if aBase > bBase { return 1 } else if aBase < bBase { From ab72dd46cd5914f7966f3ffed9372763987b9d09 Mon Sep 17 00:00:00 2001 From: lance6716 Date: Thu, 8 Apr 2021 18:14:05 +0800 Subject: [PATCH 5/5] fix goimport --- mysql/position_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql/position_test.go b/mysql/position_test.go index 806749803..aecf5d63b 100644 --- a/mysql/position_test.go +++ b/mysql/position_test.go @@ -48,4 +48,4 @@ func (t *positionCompareSuite) TestPosCompare(c *check.C) { for _, p := range ascendingPositions { c.Assert(p.Compare(p), check.Equals, 0) } -} \ No newline at end of file +}