From cb1a7a7e9847df8253bd817a9eefa533addcf8c1 Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 18 Jun 2018 14:59:39 +0800 Subject: [PATCH] fix the unsigned overflow bug --- Gopkg.lock | 4 +- river/river_test.go | 14 +- river/sync.go | 4 +- vendor/github.com/BurntSushi/toml/COPYING | 14 ++ .../toml/cmd/toml-test-decoder/COPYING | 14 ++ .../toml/cmd/toml-test-encoder/COPYING | 14 ++ .../BurntSushi/toml/cmd/tomlv/COPYING | 14 ++ .../siddontang/go-mysql/canal/canal.go | 1 + .../siddontang/go-mysql/canal/config.go | 3 + .../siddontang/go-mysql/canal/dump.go | 2 +- .../siddontang/go-mysql/canal/rows.go | 58 ++++--- .../siddontang/go-mysql/canal/sync.go | 8 +- .../siddontang/go-mysql/mysql/mariadb_gtid.go | 162 +++++++++++++++--- .../siddontang/go-mysql/schema/schema.go | 32 ++++ .../gopkg.in/birkirb/loggers.v1/LICENSE.txt | 21 +++ 15 files changed, 310 insertions(+), 55 deletions(-) create mode 100644 vendor/github.com/BurntSushi/toml/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING create mode 100644 vendor/gopkg.in/birkirb/loggers.v1/LICENSE.txt diff --git a/Gopkg.lock b/Gopkg.lock index a09cb97d..d57597bf 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -39,7 +39,7 @@ "ioutil2", "sync2" ] - revision = "2b7082d296ba89ae7ead0f977816bddefb65df9d" + revision = "bdc77568d726a8702315ec4eafda030b6abc4f43" [[projects]] branch = "master" @@ -53,7 +53,7 @@ "replication", "schema" ] - revision = "ef96922d9960d62b382118edb4a18526055e2951" + revision = "fb8f61a50302b65f98d88cf0c5d02a4631fa6ec6" [[projects]] branch = "master" diff --git a/river/river_test.go b/river/river_test.go index 0e0bcaa3..2fe42795 100644 --- a/river/river_test.go +++ b/river/river_test.go @@ -44,7 +44,8 @@ func (s *riverTestSuite) SetUpSuite(c *C) { tenum ENUM("e1", "e2", "e3"), tset SET("a", "b", "c"), tbit BIT(1) default 1, - tdatetime DATETIME DEFAULT NULL, + tdatetime DATETIME DEFAULT NULL, + ip INT UNSIGNED DEFAULT 0, PRIMARY KEY(id)) ENGINE=INNODB; ` @@ -224,6 +225,9 @@ func (s *riverTestSuite) testPrepareData(c *C) { datetime := time.Now().Format(mysql.TimeFormat) s.testExecute(c, "INSERT INTO test_river (id, title, content, tenum, tset, tdatetime, mydate) VALUES (?, ?, ?, ?, ?, ?, ?)", 16, "test datetime", "hello go 16", "e1", "a,b", datetime, 1458131094) + + // test ip + s.testExecute(c, "INSERT test_river (id, ip) VALUES (?, ?)", 17, 0) } func (s *riverTestSuite) testElasticGet(c *C, id string) *elastic.Response { @@ -320,6 +324,9 @@ func (s *riverTestSuite) TestRiver(c *C) { s.testExecute(c, fmt.Sprintf("UPDATE %s SET title = ? WHERE id = ?", table), "hello", 5+i) } + // test ip + s.testExecute(c, "UPDATE test_river set ip = ? WHERE id = ?", 3748168280, 17) + testWaitSyncDone(c, s.r) r = s.testElasticGet(c, "1") @@ -352,6 +359,11 @@ func (s *riverTestSuite) TestRiver(c *C) { c.Assert(r.Source["es_title"], Equals, "hello") } + // test ip + r = s.testElasticGet(c, "17") + c.Assert(r.Found, IsTrue) + c.Assert(r.Source["ip"], Equals, float64(3748168280)) + // alter table s.testExecute(c, "ALTER TABLE test_river ADD COLUMN new INT(10)") s.testExecute(c, "INSERT INTO test_river (id, title, content, tenum, tset, new) VALUES (?, ?, ?, ?, ?, ?)", 1000, "abc", "hello", "e1", "a,b,c", 1) diff --git a/river/sync.go b/river/sync.go index ab026029..5f09997f 100644 --- a/river/sync.go +++ b/river/sync.go @@ -418,14 +418,14 @@ func (r *River) getDocID(rule *Rule, row []interface{}) (string, error) { err error ) if rule.ID == nil { - ids, err = canal.GetPKValues(rule.TableInfo, row) + ids, err = rule.TableInfo.GetPKValues(row) if err != nil { return "", err } } else { ids = make([]interface{}, 0, len(rule.ID)) for _, column := range rule.ID { - value, err := canal.GetColumnValue(rule.TableInfo, column, row) + value, err := rule.TableInfo.GetColumnValue(column, row) if err != nil { return "", err } diff --git a/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/BurntSushi/toml/COPYING new file mode 100644 index 00000000..5a8e3325 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING new file mode 100644 index 00000000..5a8e3325 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING new file mode 100644 index 00000000..5a8e3325 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING b/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING new file mode 100644 index 00000000..5a8e3325 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/siddontang/go-mysql/canal/canal.go b/vendor/github.com/siddontang/go-mysql/canal/canal.go index aef51b79..6606c417 100644 --- a/vendor/github.com/siddontang/go-mysql/canal/canal.go +++ b/vendor/github.com/siddontang/go-mysql/canal/canal.go @@ -415,6 +415,7 @@ func (c *Canal) prepareSyncer() error { HeartbeatPeriod: c.cfg.HeartbeatPeriod, ReadTimeout: c.cfg.ReadTimeout, UseDecimal: c.cfg.UseDecimal, + SemiSyncEnabled: c.cfg.SemiSyncEnabled, } c.syncer = replication.NewBinlogSyncer(cfg) diff --git a/vendor/github.com/siddontang/go-mysql/canal/config.go b/vendor/github.com/siddontang/go-mysql/canal/config.go index d4a4ca29..991f32c3 100644 --- a/vendor/github.com/siddontang/go-mysql/canal/config.go +++ b/vendor/github.com/siddontang/go-mysql/canal/config.go @@ -63,6 +63,9 @@ type Config struct { Dump DumpConfig `toml:"dump"` UseDecimal bool `toml:"use_decimal"` + + // SemiSyncEnabled enables semi-sync or not. + SemiSyncEnabled bool `toml:"semi_sync_enabled"` } func NewConfigWithFile(name string) (*Config, error) { diff --git a/vendor/github.com/siddontang/go-mysql/canal/dump.go b/vendor/github.com/siddontang/go-mysql/canal/dump.go index 353a7597..0d086f0c 100644 --- a/vendor/github.com/siddontang/go-mysql/canal/dump.go +++ b/vendor/github.com/siddontang/go-mysql/canal/dump.go @@ -69,7 +69,7 @@ func (h *dumpParseHandler) Data(db string, table string, values []string) error } } - events := newRowsEvent(tableInfo, InsertAction, [][]interface{}{vs}) + events := newRowsEvent(tableInfo, InsertAction, [][]interface{}{vs}, nil) return h.c.eventHandler.OnRow(events) } diff --git a/vendor/github.com/siddontang/go-mysql/canal/rows.go b/vendor/github.com/siddontang/go-mysql/canal/rows.go index 2e0afa81..e246ee5a 100644 --- a/vendor/github.com/siddontang/go-mysql/canal/rows.go +++ b/vendor/github.com/siddontang/go-mysql/canal/rows.go @@ -3,16 +3,18 @@ package canal import ( "fmt" - "github.com/juju/errors" + "github.com/siddontang/go-mysql/replication" "github.com/siddontang/go-mysql/schema" ) +// The action name for sync. const ( UpdateAction = "update" InsertAction = "insert" DeleteAction = "delete" ) +// RowsEvent is the event for row replication. type RowsEvent struct { Table *schema.Table Action string @@ -22,45 +24,49 @@ type RowsEvent struct { // Two rows for one event, format is [before update row, after update row] // for update v0, only one row for a event, and we don't support this version. Rows [][]interface{} + // Header can be used to inspect the event + Header *replication.EventHeader } -func newRowsEvent(table *schema.Table, action string, rows [][]interface{}) *RowsEvent { +func newRowsEvent(table *schema.Table, action string, rows [][]interface{}, header *replication.EventHeader) *RowsEvent { e := new(RowsEvent) e.Table = table e.Action = action e.Rows = rows + e.Header = header + + e.handleUnsigned() return e } -// Get primary keys in one row for a table, a table may use multi fields as the PK -func GetPKValues(table *schema.Table, row []interface{}) ([]interface{}, error) { - indexes := table.PKColumns - if len(indexes) == 0 { - return nil, errors.Errorf("table %s has no PK", table) - } else if len(table.Columns) != len(row) { - return nil, errors.Errorf("table %s has %d columns, but row data %v len is %d", table, - len(table.Columns), row, len(row)) +func (r *RowsEvent) handleUnsigned() { + // Handle Unsigned Columns here, for binlog replication, we can't know the integer is unsigned or not, + // so we use int type but this may cause overflow outside sometimes, so we must convert to the really . + // unsigned type + if len(r.Table.UnsignedColumns) == 0 { + return } - values := make([]interface{}, 0, len(indexes)) - - for _, index := range indexes { - values = append(values, row[index]) + for i := 0; i < len(r.Rows); i++ { + for _, index := range r.Table.UnsignedColumns { + switch t := r.Rows[i][index].(type) { + case int8: + r.Rows[i][index] = uint8(t) + case int16: + r.Rows[i][index] = uint16(t) + case int32: + r.Rows[i][index] = uint32(t) + case int64: + r.Rows[i][index] = uint64(t) + case int: + r.Rows[i][index] = uint(t) + default: + // nothing to do + } + } } - - return values, nil -} - -// Get term column's value -func GetColumnValue(table *schema.Table, column string, row []interface{}) (interface{}, error) { - index := table.FindColumn(column) - if index == -1 { - return nil, errors.Errorf("table %s has no column name %s", table, column) - } - - return row[index], nil } // String implements fmt.Stringer interface. diff --git a/vendor/github.com/siddontang/go-mysql/canal/sync.go b/vendor/github.com/siddontang/go-mysql/canal/sync.go index 305b2688..38d0b107 100644 --- a/vendor/github.com/siddontang/go-mysql/canal/sync.go +++ b/vendor/github.com/siddontang/go-mysql/canal/sync.go @@ -98,7 +98,11 @@ func (c *Canal) runSyncBinlog() error { } case *replication.MariadbGTIDEvent: // try to save the GTID later - gtid := &e.GTID + gtid, err := mysql.ParseMariadbGTIDSet(e.GTID.String()) + if err != nil { + return errors.Trace(err) + } + c.master.UpdateGTID(gtid) if err := c.eventHandler.OnGTID(gtid); err != nil { return errors.Trace(err) @@ -186,7 +190,7 @@ func (c *Canal) handleRowsEvent(e *replication.BinlogEvent) error { default: return errors.Errorf("%s not supported now", e.Header.EventType) } - events := newRowsEvent(t, action, ev.Rows) + events := newRowsEvent(t, action, ev.Rows, e.Header) return c.eventHandler.OnRow(events) } diff --git a/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go index d2573fe1..6f73767e 100644 --- a/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go +++ b/vendor/github.com/siddontang/go-mysql/mysql/mariadb_gtid.go @@ -1,21 +1,24 @@ package mysql import ( + "bytes" "fmt" "strconv" "strings" "github.com/juju/errors" + "github.com/siddontang/go/hack" ) +// MariadbGTID represent mariadb gtid, [domain ID]-[server-id]-[sequence] type MariadbGTID struct { DomainID uint32 ServerID uint32 SequenceNumber uint64 } -// We don't support multi source replication, so the mariadb gtid set may have only domain-server-sequence -func ParseMariadbGTIDSet(str string) (GTIDSet, error) { +// ParseMariadbGTID parses mariadb gtid, [domain ID]-[server-id]-[sequence] +func ParseMariadbGTID(str string) (*MariadbGTID, error) { if len(str) == 0 { return &MariadbGTID{0, 0, 0}, nil } @@ -57,41 +60,158 @@ func (gtid *MariadbGTID) String() string { return fmt.Sprintf("%d-%d-%d", gtid.DomainID, gtid.ServerID, gtid.SequenceNumber) } -func (gtid *MariadbGTID) Encode() []byte { - return []byte(gtid.String()) +// Contain return whether one mariadb gtid covers another mariadb gtid +func (gtid *MariadbGTID) Contain(other *MariadbGTID) bool { + return gtid.DomainID == other.DomainID && gtid.SequenceNumber >= other.SequenceNumber } -func (gtid *MariadbGTID) Equal(o GTIDSet) bool { - other, ok := o.(*MariadbGTID) - if !ok { - return false +// Clone clones a mariadb gtid +func (gtid *MariadbGTID) Clone() *MariadbGTID { + o := new(MariadbGTID) + *o = *gtid + return o +} + +func (gtid *MariadbGTID) forward(newer *MariadbGTID) error { + if newer.DomainID != gtid.DomainID { + return errors.Errorf("%s is not same with doamin of %s", newer, gtid) + } + + if newer.SequenceNumber <= gtid.SequenceNumber { + return errors.Errorf("out of order binlog appears with gtid %s vs current position gtid %s", newer, gtid) } - return *gtid == *other + gtid.ServerID = newer.ServerID + gtid.SequenceNumber = newer.SequenceNumber + return nil } -func (gtid *MariadbGTID) Contain(o GTIDSet) bool { - other, ok := o.(*MariadbGTID) - if !ok { - return false +// MariadbGTIDSet is a set of mariadb gtid +type MariadbGTIDSet struct { + Sets map[uint32]*MariadbGTID +} + +// ParseMariadbGTIDSet parses str into mariadb gtid sets +func ParseMariadbGTIDSet(str string) (GTIDSet, error) { + s := new(MariadbGTIDSet) + s.Sets = make(map[uint32]*MariadbGTID) + if str == "" { + return s, nil } - return gtid.DomainID == other.DomainID && gtid.SequenceNumber >= other.SequenceNumber + sp := strings.Split(str, ",") + + //todo, handle redundant same uuid + for i := 0; i < len(sp); i++ { + err := s.Update(sp[i]) + if err != nil { + return nil, errors.Trace(err) + } + } + return s, nil } +// AddSet adds mariadb gtid into mariadb gtid set +func (s *MariadbGTIDSet) AddSet(gtid *MariadbGTID) error { + if gtid == nil { + return nil + } -func (gtid *MariadbGTID) Update(GTIDStr string) error { - newGTID, err := ParseMariadbGTIDSet(GTIDStr) + o, ok := s.Sets[gtid.DomainID] + if ok { + err := o.forward(gtid) + if err != nil { + return errors.Trace(err) + } + } else { + s.Sets[gtid.DomainID] = gtid + } + + return nil +} + +// Update updates mariadb gtid set +func (s *MariadbGTIDSet) Update(GTIDStr string) error { + gtid, err := ParseMariadbGTID(GTIDStr) if err != nil { return err } - *gtid = *(newGTID.(*MariadbGTID)) + err = s.AddSet(gtid) + return errors.Trace(err) +} + - return nil +func (s *MariadbGTIDSet) String() string { + return hack.String(s.Encode()) } -func (gtid *MariadbGTID) Clone() GTIDSet { - clone := new(MariadbGTID) - *clone = *gtid +// Encode encodes mariadb gtid set +func (s *MariadbGTIDSet) Encode() []byte { + var buf bytes.Buffer + sep := "" + for _, gtid := range s.Sets { + buf.WriteString(sep) + buf.WriteString(gtid.String()) + sep = "," + } + + return buf.Bytes() +} + +// Clone clones a mariadb gtid set +func (s *MariadbGTIDSet) Clone() GTIDSet { + clone := &MariadbGTIDSet{ + Sets: make(map[uint32]*MariadbGTID), + } + for domainID, gtid := range s.Sets { + clone.Sets[domainID] = gtid.Clone() + } + return clone } + +// Equal returns true if two mariadb gtid set is same, otherwise return false +func (s *MariadbGTIDSet) Equal(o GTIDSet) bool { + other, ok := o.(*MariadbGTIDSet) + if !ok { + return false + } + + if len(other.Sets) != len(s.Sets) { + return false + } + + for domainID, gtid := range other.Sets { + o, ok := s.Sets[domainID] + if !ok { + return false + } + + if *gtid != *o { + return false + } + } + + return true +} + +// Contain return whether one mariadb gtid set covers another mariadb gtid set +func (s *MariadbGTIDSet) Contain(o GTIDSet) bool { + other, ok := o.(*MariadbGTIDSet) + if !ok { + return false + } + + for doaminID, gtid := range other.Sets { + o, ok := s.Sets[doaminID] + if !ok { + return false + } + + if !o.Contain(gtid) { + return false + } + } + + return true +} diff --git a/vendor/github.com/siddontang/go-mysql/schema/schema.go b/vendor/github.com/siddontang/go-mysql/schema/schema.go index 5e64b4c1..cb3740ab 100644 --- a/vendor/github.com/siddontang/go-mysql/schema/schema.go +++ b/vendor/github.com/siddontang/go-mysql/schema/schema.go @@ -55,6 +55,8 @@ type Table struct { Columns []TableColumn Indexes []*Index PKColumns []int + + UnsignedColumns []int } func (ta *Table) String() string { @@ -108,6 +110,7 @@ func (ta *Table) AddColumn(name string, columnType string, collation string, ext if strings.Contains(columnType, "unsigned") || strings.Contains(columnType, "zerofill") { ta.Columns[index].IsUnsigned = true + ta.UnsignedColumns = append(ta.UnsignedColumns, index) } if extra == "auto_increment" { @@ -361,3 +364,32 @@ func (ta *Table) fetchPrimaryKeyColumns() error { return nil } + +// Get primary keys in one row for a table, a table may use multi fields as the PK +func (ta *Table) GetPKValues(row []interface{}) ([]interface{}, error) { + indexes := ta.PKColumns + if len(indexes) == 0 { + return nil, errors.Errorf("table %s has no PK", ta) + } else if len(ta.Columns) != len(row) { + return nil, errors.Errorf("table %s has %d columns, but row data %v len is %d", ta, + len(ta.Columns), row, len(row)) + } + + values := make([]interface{}, 0, len(indexes)) + + for _, index := range indexes { + values = append(values, row[index]) + } + + return values, nil +} + +// Get term column's value +func (ta *Table) GetColumnValue(column string, row []interface{}) (interface{}, error) { + index := ta.FindColumn(column) + if index == -1 { + return nil, errors.Errorf("table %s has no column name %s", ta, column) + } + + return row[index], nil +} diff --git a/vendor/gopkg.in/birkirb/loggers.v1/LICENSE.txt b/vendor/gopkg.in/birkirb/loggers.v1/LICENSE.txt new file mode 100644 index 00000000..7789a925 --- /dev/null +++ b/vendor/gopkg.in/birkirb/loggers.v1/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Birkir A. Barkarson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.