github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/event/generator_test.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 "fmt" 18 "os" 19 "path/filepath" 20 "testing" 21 22 gmysql "github.com/go-mysql-org/go-mysql/mysql" 23 "github.com/go-mysql-org/go-mysql/replication" 24 "github.com/pingcap/tiflow/dm/pkg/gtid" 25 "github.com/stretchr/testify/require" 26 ) 27 28 func TestGenerateForMySQL(t *testing.T) { 29 t.Parallel() 30 var ( 31 flavor = gmysql.MySQLFlavor 32 serverID uint32 = 101 33 latestXID uint64 = 10 34 ) 35 36 previousGTIDSetStr := "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14,406a3f61-690d-11e7-87c5-6c92bf46f384:1-94321383,53bfca22-690d-11e7-8a62-18ded7a37b78:1-495,686e1ab6-c47e-11e7-a42c-6c92bf46f384:1-34981190,03fc0263-28c7-11e7-a653-6c0b84d59f30:1-7041423,05474d3c-28c7-11e7-8352-203db246dd3d:1-170,10b039fc-c843-11e7-8f6a-1866daf8d810:1-308290454" 37 previousGTIDSet, err := gtid.ParserGTID(flavor, previousGTIDSetStr) 38 require.Nil(t, err) 39 require.NotNil(t, previousGTIDSet) 40 41 // mutil GTID in latestGTID 42 latestGTIDStr := "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14" 43 latestGTID, err := gtid.ParserGTID(flavor, latestGTIDStr) 44 require.Nil(t, err) 45 require.NotNil(t, latestGTID) 46 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 47 require.NotNil(t, err) 48 49 // latestGTID not one of the latest previousGTIDSet, UUID not found 50 latestGTIDStr = "11111111-2343-11e7-be21-6c0b84d59f30:14" 51 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 52 require.Nil(t, err) 53 require.NotNil(t, latestGTID) 54 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 55 require.NotNil(t, err) 56 57 // latestGTID not one of the latest previousGTIDSet, interval mismatch 58 latestGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:13" 59 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 60 require.Nil(t, err) 61 require.NotNil(t, latestGTID) 62 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 63 require.NotNil(t, err) 64 65 latestGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:14" 66 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 67 require.Nil(t, err) 68 require.NotNil(t, latestGTID) 69 70 testGenerate(t, flavor, serverID, latestGTID, previousGTIDSet, latestXID) 71 } 72 73 func TestGenerateForMariaDB(t *testing.T) { 74 t.Parallel() 75 var ( 76 flavor = gmysql.MariaDBFlavor 77 serverID uint32 = 101 78 latestXID uint64 = 10 79 ) 80 81 previousGTIDSetStr := "1-101-12,2-2-3,3-3-8,4-4-4" 82 previousGTIDSet, err := gtid.ParserGTID(flavor, previousGTIDSetStr) 83 require.Nil(t, err) 84 require.NotNil(t, previousGTIDSet) 85 86 // multi GTID in latestGTID 87 latestGTIDStr := "1-101-12,2-2-23" 88 latestGTID, err := gtid.ParserGTID(flavor, latestGTIDStr) 89 require.Nil(t, err) 90 require.NotNil(t, latestGTID) 91 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 92 require.NotNil(t, err) 93 94 // latestGTID not one of previousGTIDSet, domain-id mismatch 95 latestGTIDStr = "5-101-12" 96 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 97 require.Nil(t, err) 98 require.NotNil(t, latestGTID) 99 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 100 require.NotNil(t, err) 101 102 // latestGTID not one of previousGTIDSet, sequence-number not equal 103 latestGTIDStr = "1-101-13" 104 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 105 require.Nil(t, err) 106 require.NotNil(t, latestGTID) 107 _, err = NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 108 require.NotNil(t, err) 109 110 latestGTIDStr = "1-101-12" 111 latestGTID, err = gtid.ParserGTID(flavor, latestGTIDStr) 112 require.Nil(t, err) 113 require.NotNil(t, latestGTID) 114 115 // server-id mismatch 116 _, err = NewGenerator(flavor, 100, 0, latestGTID, previousGTIDSet, latestXID) 117 require.NotNil(t, err) 118 119 testGenerate(t, flavor, serverID, latestGTID, previousGTIDSet, latestXID) 120 } 121 122 func testGenerate(t *testing.T, flavor string, serverID uint32, latestGTID gmysql.GTIDSet, previousGTIDSet gmysql.GTIDSet, latestXID uint64) { 123 t.Helper() 124 // write some events to file 125 dir := t.TempDir() 126 filename := filepath.Join(dir, "mysql-bin-test.000001") 127 f, err := os.Create(filename) 128 require.Nil(t, err) 129 defer f.Close() 130 131 g, err := NewGenerator(flavor, serverID, 0, latestGTID, previousGTIDSet, latestXID) 132 require.Nil(t, err) 133 allEvents := make([]*replication.BinlogEvent, 0, 20) 134 allEventTypes := make([]replication.EventType, 0, 50) 135 136 // file header 137 currentEvents, data, err := g.GenFileHeader(0) 138 require.Nil(t, err) 139 _, err = f.Write(data) 140 require.Nil(t, err) 141 allEvents = append(allEvents, currentEvents...) 142 allEventTypes = append(allEventTypes, replication.FORMAT_DESCRIPTION_EVENT, previousGTIDEventType(t, flavor)) 143 144 // CREATE DATABASE `db` 145 schema := "db" 146 currentEvents, data, err = g.GenCreateDatabaseEvents(schema) 147 require.Nil(t, err) 148 _, err = f.Write(data) 149 require.Nil(t, err) 150 allEvents = append(allEvents, currentEvents...) 151 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT) 152 153 // CREATE TABLE `db`.`tbl` (c1 INT, c2 TEXT) 154 table := "tbl" 155 query := fmt.Sprintf("CREATE TABLE `%s`.`%s` (c1 INT, c2 TEXT)", schema, table) 156 currentEvents, data, err = g.GenCreateTableEvents(schema, query) 157 require.Nil(t, err) 158 _, err = f.Write(data) 159 require.Nil(t, err) 160 allEvents = append(allEvents, currentEvents...) 161 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT) 162 163 // INSERT INTO `db`.`tbl` VALUES (1, "string 1") 164 var ( 165 tableID uint64 = 8 166 columnType = []byte{gmysql.MYSQL_TYPE_LONG, gmysql.MYSQL_TYPE_STRING} 167 ) 168 insertRows := make([][]interface{}, 0, 1) 169 insertRows = append(insertRows, []interface{}{int32(1), "string 1"}) 170 dmlData := []*DMLData{ 171 { 172 TableID: tableID, 173 Schema: schema, 174 Table: table, 175 ColumnType: columnType, 176 Rows: insertRows, 177 }, 178 } 179 eventType := replication.WRITE_ROWS_EVENTv2 180 currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0) 181 require.Nil(t, err) 182 _, err = f.Write(data) 183 require.Nil(t, err) 184 allEvents = append(allEvents, currentEvents...) 185 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT) 186 187 // INSERT INTO `db`.`tbl` VALUES (11, "string 11"), (12, "string 12") 188 // INSERT INTO `db`.`tbl` VALUES (13, "string 13"), 189 insertRows1 := make([][]interface{}, 0, 2) 190 insertRows1 = append(insertRows1, []interface{}{int32(11), "string 11"}, []interface{}{int32(12), "string 12"}) 191 insertRows2 := make([][]interface{}, 0, 1) 192 insertRows2 = append(insertRows2, []interface{}{int32(13), "string 13"}) 193 dmlData = []*DMLData{ 194 { 195 TableID: tableID, 196 Schema: schema, 197 Table: table, 198 ColumnType: columnType, 199 Rows: insertRows1, 200 }, 201 { 202 TableID: tableID, 203 Schema: schema, 204 Table: table, 205 ColumnType: columnType, 206 Rows: insertRows2, 207 }, 208 } 209 currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0) 210 require.Nil(t, err) 211 _, err = f.Write(data) 212 require.Nil(t, err) 213 allEvents = append(allEvents, currentEvents...) 214 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT) 215 216 // UPDATE `db`.`tbl` SET c2="another string 11" WHERE c1=11 217 // UPDATE `db`.`tbl` SET c1=120, c2="another string 120" WHERE C1=12 218 updateRows1 := make([][]interface{}, 0, 2) 219 updateRows1 = append(updateRows1, []interface{}{int32(11), "string 11"}, []interface{}{int32(11), "another string 11"}) 220 updateRows2 := make([][]interface{}, 0, 2) 221 updateRows2 = append(updateRows2, []interface{}{int32(12), "string 12"}, []interface{}{int32(120), "another string 120"}) 222 dmlData = []*DMLData{ 223 { 224 TableID: tableID, 225 Schema: schema, 226 Table: table, 227 ColumnType: columnType, 228 Rows: updateRows1, 229 }, 230 { 231 TableID: tableID, 232 Schema: schema, 233 Table: table, 234 ColumnType: columnType, 235 Rows: updateRows2, 236 }, 237 } 238 eventType = replication.UPDATE_ROWS_EVENTv2 239 currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0) 240 require.Nil(t, err) 241 _, err = f.Write(data) 242 require.Nil(t, err) 243 allEvents = append(allEvents, currentEvents...) 244 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT) 245 246 // DELETE FROM `db`.`tbl` WHERE c1=13 247 deleteRows := make([][]interface{}, 0, 1) 248 deleteRows = append(deleteRows, []interface{}{int32(13), "string 13"}) 249 dmlData = []*DMLData{ 250 { 251 TableID: tableID, 252 Schema: schema, 253 Table: table, 254 ColumnType: columnType, 255 Rows: deleteRows, 256 }, 257 } 258 eventType = replication.DELETE_ROWS_EVENTv2 259 currentEvents, data, err = g.GenDMLEvents(eventType, dmlData, 0) 260 require.Nil(t, err) 261 _, err = f.Write(data) 262 require.Nil(t, err) 263 allEvents = append(allEvents, currentEvents...) 264 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT, replication.TABLE_MAP_EVENT, eventType, replication.XID_EVENT) 265 266 // ALTER TABLE 267 query = fmt.Sprintf("ALTER TABLE `%s`.`%s` ADD COLUMN c3 INT", schema, table) 268 currentEvents, data, err = g.GenDDLEvents(schema, query, 0) 269 require.Nil(t, err) 270 _, err = f.Write(data) 271 require.Nil(t, err) 272 allEvents = append(allEvents, currentEvents...) 273 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT) 274 275 // DROP TABLE `db`.`tbl` 276 currentEvents, data, err = g.GenDropTableEvents(schema, table) 277 require.Nil(t, err) 278 _, err = f.Write(data) 279 require.Nil(t, err) 280 allEvents = append(allEvents, currentEvents...) 281 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT) 282 283 // DROP DATABASE `db` 284 currentEvents, data, err = g.GenDropDatabaseEvents(schema) 285 require.Nil(t, err) 286 _, err = f.Write(data) 287 require.Nil(t, err) 288 allEvents = append(allEvents, currentEvents...) 289 allEventTypes = append(allEventTypes, gtidEventType(t, flavor), replication.QUERY_EVENT) 290 291 // parse the file 292 count := 0 293 onEventFunc := func(e *replication.BinlogEvent) error { 294 require.Equal(t, allEventTypes[count], e.Header.EventType) 295 require.Equal(t, allEvents[count].RawData, e.RawData) 296 count++ 297 return nil 298 } 299 300 parser2 := replication.NewBinlogParser() 301 parser2.SetVerifyChecksum(true) 302 err = parser2.ParseFile(filename, 0, onEventFunc) 303 require.Nil(t, err) 304 } 305 306 func previousGTIDEventType(t *testing.T, flavor string) replication.EventType { 307 t.Helper() 308 switch flavor { 309 case gmysql.MySQLFlavor: 310 return replication.PREVIOUS_GTIDS_EVENT 311 case gmysql.MariaDBFlavor: 312 return replication.MARIADB_GTID_LIST_EVENT 313 default: 314 t.Fatalf("unsupported flavor %s", flavor) 315 return replication.PREVIOUS_GTIDS_EVENT // hack for compiler 316 } 317 } 318 319 func gtidEventType(t *testing.T, flavor string) replication.EventType { 320 t.Helper() 321 switch flavor { 322 case gmysql.MySQLFlavor: 323 return replication.GTID_EVENT 324 case gmysql.MariaDBFlavor: 325 return replication.MARIADB_GTID_EVENT 326 default: 327 t.Fatalf("unsupported flavor %s", flavor) 328 return replication.GTID_EVENT // hack for compiler 329 } 330 }