github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/common.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package event 15 16 import ( 17 "bytes" 18 "time" 19 20 gmysql "github.com/go-mysql-org/go-mysql/mysql" 21 "github.com/go-mysql-org/go-mysql/replication" 22 "github.com/pingcap/tiflow/dm/pkg/gtid" 23 "github.com/pingcap/tiflow/dm/pkg/terror" 24 ) 25 26 // DDLDMLResult represents a binlog event result for generated DDL/DML. 27 type DDLDMLResult struct { 28 Events []*replication.BinlogEvent 29 Data []byte // data contain all events 30 LatestPos uint32 31 LatestGTID gmysql.GTIDSet 32 } 33 34 // GenCommonFileHeader generates a common binlog file header. 35 // for MySQL: 36 // 1. BinLogFileHeader, [ fe `bin` ] 37 // 2. FormatDescriptionEvent 38 // 3. PreviousGTIDsEvent, depends on genGTID 39 // 40 // for MariaDB: 41 // 1. BinLogFileHeader, [ fe `bin` ] 42 // 2. FormatDescriptionEvent 43 // 3. MariadbGTIDListEvent, depends on genGTID 44 func GenCommonFileHeader(flavor string, serverID uint32, gSet gmysql.GTIDSet, genGTID bool, ts int64) ([]*replication.BinlogEvent, []byte, error) { 45 if ts == 0 { 46 ts = time.Now().Unix() 47 } 48 var ( 49 header = &replication.EventHeader{ 50 Timestamp: uint32(ts), 51 ServerID: serverID, 52 Flags: defaultHeaderFlags, 53 } 54 latestPos uint32 55 prevGTIDsEv *replication.BinlogEvent 56 buf bytes.Buffer 57 events []*replication.BinlogEvent 58 ) 59 60 _, err := buf.Write(replication.BinLogFileHeader) 61 if err != nil { 62 return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write binlog file header % X", replication.BinLogFileHeader) 63 } 64 latestPos += uint32(len(replication.BinLogFileHeader)) 65 66 formatDescEv, err := GenFormatDescriptionEvent(header, latestPos) 67 if err != nil { 68 return nil, nil, terror.Annotate(err, "generate FormatDescriptionEvent") 69 } 70 _, err = buf.Write(formatDescEv.RawData) 71 if err != nil { 72 return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write FormatDescriptionEvent % X", formatDescEv.RawData) 73 } 74 latestPos += uint32(len(formatDescEv.RawData)) // update latestPos 75 events = append(events, formatDescEv) 76 77 if genGTID { 78 switch flavor { 79 case gmysql.MySQLFlavor: 80 prevGTIDsEv, err = GenPreviousGTIDsEvent(header, latestPos, gSet) 81 case gmysql.MariaDBFlavor: 82 prevGTIDsEv, err = GenMariaDBGTIDListEvent(header, latestPos, gSet) 83 default: 84 return nil, nil, terror.ErrBinlogFlavorNotSupport.Generate(flavor) 85 } 86 if err != nil { 87 return nil, nil, terror.Annotate(err, "generate PreviousGTIDsEvent/MariadbGTIDListEvent") 88 } 89 90 _, err = buf.Write(prevGTIDsEv.RawData) 91 if err != nil { 92 return nil, nil, terror.ErrBinlogWriteDataToBuffer.AnnotateDelegate(err, "write PreviousGTIDsEvent/MariadbGTIDListEvent % X", prevGTIDsEv.RawData) 93 } 94 events = append(events, prevGTIDsEv) 95 } 96 97 return events, buf.Bytes(), nil 98 } 99 100 // GenCommonGTIDEvent generates a common GTID event. 101 func GenCommonGTIDEvent(flavor string, serverID uint32, latestPos uint32, gSet gmysql.GTIDSet, anonymous bool, ts int64) (*replication.BinlogEvent, error) { 102 singleGTID, err := verifySingleGTID(flavor, gSet) 103 if err != nil { 104 return nil, terror.Annotate(err, "verify single GTID in set") 105 } 106 107 if ts == 0 { 108 ts = time.Now().Unix() 109 } 110 var ( 111 header = &replication.EventHeader{ 112 Timestamp: uint32(ts), 113 ServerID: serverID, 114 Flags: defaultHeaderFlags, 115 } 116 gtidEv *replication.BinlogEvent 117 ) 118 119 switch flavor { 120 case gmysql.MySQLFlavor: 121 uuidSet := singleGTID.(*gmysql.UUIDSet) 122 interval := uuidSet.Intervals[0] 123 if anonymous { 124 gtidEv, err = GenAnonymousGTIDEvent(header, latestPos, defaultGTIDFlags, defaultLastCommitted, defaultSequenceNumber) 125 } else { 126 gtidEv, err = GenGTIDEvent(header, latestPos, defaultGTIDFlags, uuidSet.SID.String(), interval.Start, defaultLastCommitted, defaultSequenceNumber) 127 } 128 case gmysql.MariaDBFlavor: 129 mariaGTID := singleGTID.(*gmysql.MariadbGTID) 130 if mariaGTID.ServerID != header.ServerID { 131 return nil, terror.ErrBinlogMariaDBServerIDMismatch.Generate(mariaGTID.ServerID, header.ServerID) 132 } 133 gtidEv, err = GenMariaDBGTIDEvent(header, latestPos, mariaGTID.SequenceNumber, mariaGTID.DomainID) 134 if err != nil { 135 return gtidEv, err 136 } 137 // in go-mysql, set ServerID in parseEvent. we try to set it directly 138 gtidEvBody := gtidEv.Event.(*replication.MariadbGTIDEvent) 139 gtidEvBody.GTID.ServerID = header.ServerID 140 default: 141 err = terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor) 142 } 143 return gtidEv, err 144 } 145 146 // GTIDIncrease returns a new GTID with GNO/SequenceNumber +1. 147 func GTIDIncrease(flavor string, gSet gmysql.GTIDSet) (gmysql.GTIDSet, error) { 148 singleGTID, err := verifySingleGTID(flavor, gSet) 149 if err != nil { 150 return nil, terror.Annotate(err, "verify single GTID in set") 151 } 152 clone := gSet.Clone() 153 154 switch flavor { 155 case gmysql.MySQLFlavor: 156 uuidSet := singleGTID.(*gmysql.UUIDSet) 157 uuidSet.Intervals[0].Start++ 158 uuidSet.Intervals[0].Stop++ 159 gtidSet := new(gmysql.MysqlGTIDSet) 160 gtidSet.Sets = map[string]*gmysql.UUIDSet{uuidSet.SID.String(): uuidSet} 161 clone = gtidSet 162 case gmysql.MariaDBFlavor: 163 mariaGTID := singleGTID.(*gmysql.MariadbGTID) 164 mariaGTID.SequenceNumber++ 165 gtidSet := new(gmysql.MariadbGTIDSet) 166 gtidSet.Sets = map[uint32]map[uint32]*gmysql.MariadbGTID{ 167 mariaGTID.DomainID: { 168 mariaGTID.ServerID: mariaGTID, 169 }, 170 } 171 clone = gtidSet 172 default: 173 err = terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor) 174 } 175 return clone, err 176 } 177 178 // verifySingleGTID verifies gSet whether only containing a single valid GTID. 179 func verifySingleGTID(flavor string, gSet gmysql.GTIDSet) (interface{}, error) { 180 if gtid.CheckGTIDSetEmpty(gSet) { 181 return nil, terror.ErrBinlogEmptyGTID.Generate() 182 } 183 184 switch flavor { 185 case gmysql.MySQLFlavor: 186 mysqlGTIDs, ok := gSet.(*gmysql.MysqlGTIDSet) 187 if !ok { 188 return nil, terror.ErrBinlogGTIDMySQLNotValid.Generate(gSet) 189 } 190 if len(mysqlGTIDs.Sets) != 1 { 191 return nil, terror.ErrBinlogOnlyOneGTIDSupport.Generate(len(mysqlGTIDs.Sets), gSet) 192 } 193 var uuidSet *gmysql.UUIDSet 194 for _, uuidSet = range mysqlGTIDs.Sets { 195 } 196 intervals := uuidSet.Intervals 197 if intervals.Len() != 1 { 198 return nil, terror.ErrBinlogOnlyOneIntervalInUUID.Generate(intervals.Len(), gSet) 199 } 200 interval := intervals[0] 201 if interval.Stop != interval.Start+1 { 202 return nil, terror.ErrBinlogIntervalValueNotValid.Generate(interval, gSet) 203 } 204 return uuidSet, nil 205 case gmysql.MariaDBFlavor: 206 mariaGTIDs, ok := gSet.(*gmysql.MariadbGTIDSet) 207 if !ok { 208 return nil, terror.ErrBinlogGTIDMariaDBNotValid.Generate(gSet) 209 } 210 gtidCount := 0 211 var mariaGTID *gmysql.MariadbGTID 212 for _, set := range mariaGTIDs.Sets { 213 gtidCount += len(set) 214 for _, mariaGTID = range set { 215 } 216 } 217 if gtidCount != 1 { 218 return nil, terror.ErrBinlogOnlyOneGTIDSupport.Generate(gtidCount, gSet) 219 } 220 return mariaGTID, nil 221 default: 222 return nil, terror.ErrBinlogGTIDSetNotValid.Generate(gSet, flavor) 223 } 224 }