github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/binlog/pos_finder_test.go (about) 1 // Copyright 2021 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 binlog 15 16 import ( 17 "bytes" 18 "context" 19 "fmt" 20 "os" 21 "path" 22 "strconv" 23 "testing" 24 "time" 25 26 "github.com/go-mysql-org/go-mysql/mysql" 27 "github.com/go-mysql-org/go-mysql/replication" 28 "github.com/pingcap/tiflow/dm/pkg/binlog/event" 29 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 30 "github.com/pingcap/tiflow/dm/pkg/log" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func genBinlogFile(generator *event.Generator, start time.Time, nextFile string) ([]*replication.BinlogEvent, []byte) { 35 insertDMLData := []*event.DMLData{ 36 { 37 TableID: uint64(1), 38 Schema: fmt.Sprintf("db_%d", 1), 39 Table: strconv.Itoa(1), 40 ColumnType: []byte{mysql.MYSQL_TYPE_INT24}, 41 Rows: [][]interface{}{{int32(1)}, {int32(2)}}, 42 }, 43 } 44 allEvents := make([]*replication.BinlogEvent, 0) 45 var buf bytes.Buffer 46 events, data, _ := generator.GenFileHeader(start.Add(1 * time.Second).Unix()) 47 allEvents = append(allEvents, events...) 48 buf.Write(data) 49 50 events, data, _ = generator.GenDDLEvents("test", "create table t(id int)", start.Add(2*time.Second).Unix()) 51 allEvents = append(allEvents, events...) 52 buf.Write(data) 53 54 events, data, _ = generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, start.Add(3*time.Second).Unix()) 55 allEvents = append(allEvents, events...) 56 buf.Write(data) 57 58 events, data, _ = generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, start.Add(5*time.Second).Unix()) 59 allEvents = append(allEvents, events...) 60 buf.Write(data) 61 62 ev, data, _ := generator.Rotate(nextFile, start.Add(5*time.Second).Unix()) 63 allEvents = append(allEvents, ev) 64 buf.Write(data) 65 66 return allEvents, buf.Bytes() 67 } 68 69 func TestTransBoundary(t *testing.T) { 70 t.Parallel() 71 flavor := "mysql" 72 relayDir := t.TempDir() 73 beforeTime := time.Now() 74 latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1" 75 generator, _ := event.NewGeneratorV2(flavor, "5.6.0", latestGTIDStr, false) 76 insertDMLData := []*event.DMLData{ 77 { 78 TableID: uint64(1), 79 Schema: fmt.Sprintf("db_%d", 1), 80 Table: strconv.Itoa(1), 81 ColumnType: []byte{mysql.MYSQL_TYPE_INT24}, 82 Rows: [][]interface{}{{int32(1)}, {int32(2)}}, 83 }, 84 } 85 var buf bytes.Buffer 86 _, data, err := generator.GenFileHeader(beforeTime.Add(1 * time.Second).Unix()) 87 require.Nil(t, err) 88 buf.Write(data) 89 90 // first transaction, timestamp of BEGIN = beforeTime.Add(2*time.Second) 91 // timestamp of other events inside this transaction = beforeTime.Add(3 * time.Second) 92 ts := beforeTime.Add(2 * time.Second).Unix() 93 header := &replication.EventHeader{ 94 Timestamp: uint32(ts), 95 ServerID: 11, 96 Flags: 0x01, 97 } 98 beginEvent, _ := event.GenQueryEvent(header, generator.LatestPos, 1, 1, 0, []byte("0"), []byte("test"), []byte("BEGIN")) 99 buf.Write(beginEvent.RawData) 100 101 ts = beforeTime.Add(3 * time.Second).Unix() 102 header.Timestamp = uint32(ts) 103 mapEvent, _ := event.GenTableMapEvent(header, beginEvent.Header.LogPos, 1, []byte("test"), []byte("t"), []byte{mysql.MYSQL_TYPE_INT24}) 104 buf.Write(mapEvent.RawData) 105 rowsEvent, _ := event.GenRowsEvent(header, mapEvent.Header.LogPos, replication.WRITE_ROWS_EVENTv2, 1, 1, [][]interface{}{{int32(1)}, {int32(2)}}, []byte{mysql.MYSQL_TYPE_INT24}, mapEvent) 106 buf.Write(rowsEvent.RawData) 107 xidEvent, _ := event.GenXIDEvent(header, rowsEvent.Header.LogPos, 1) 108 buf.Write(xidEvent.RawData) 109 110 // second transaction, timestamp of all events = beforeTime.Add(3 * time.Second) 111 generator.LatestPos = xidEvent.Header.LogPos 112 dmlEvents, data, _ := generator.GenDMLEvents(replication.WRITE_ROWS_EVENTv2, insertDMLData, ts) 113 buf.Write(data) 114 115 require.Equal(t, uint32(buf.Len()), dmlEvents[len(dmlEvents)-1].Header.LogPos) 116 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), buf.Bytes(), 0o644) 117 118 { 119 tcctx := tcontext.NewContext(context.Background(), log.L()) 120 finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir) 121 location, posType, err := finder.FindByTimestamp(ts) 122 require.Nil(t, err) 123 // start of second transaction 124 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: xidEvent.Header.LogPos}, location.Position) 125 require.Equal(t, "", location.GTIDSetStr()) 126 require.Equal(t, InRangeBinlogPos, posType) 127 } 128 } 129 130 func TestMySQL56NoGTID(t *testing.T) { 131 t.Parallel() 132 flavor := "mysql" 133 relayDir := t.TempDir() 134 beforeTime := time.Now() 135 latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1" 136 137 generator, _ := event.NewGeneratorV2(flavor, "5.6.0", latestGTIDStr, false) 138 139 file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002") 140 require.Equal(t, 11, len(file1Events)) 141 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644) 142 file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003") 143 require.Equal(t, 11, len(file2Events)) 144 145 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644) 146 file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004") 147 require.Equal(t, 11, len(file3Events)) 148 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644) 149 150 tcctx := tcontext.NewContext(context.Background(), log.L()) 151 { 152 var targetEventStart uint32 153 var targetEvent *replication.BinlogEvent 154 for _, ev := range file1Events { 155 if e, ok := ev.Event.(*replication.QueryEvent); ok && string(e.Query) == "BEGIN" { 156 targetEvent = ev 157 break 158 } 159 targetEventStart = ev.Header.LogPos 160 } 161 finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir) 162 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 163 require.Nil(t, err) 164 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position) 165 require.Equal(t, "", location.GTIDSetStr()) 166 require.Equal(t, InRangeBinlogPos, posType) 167 } 168 { 169 targetEventStart := file2Events[len(file2Events)-1].Header.LogPos 170 finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir) 171 location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp)) 172 require.Nil(t, err) 173 require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position) 174 require.Equal(t, "", location.GTIDSetStr()) 175 require.Equal(t, InRangeBinlogPos, posType) 176 } 177 { 178 var targetEventStart uint32 179 var targetEvent *replication.BinlogEvent 180 for _, ev := range file3Events { 181 if _, ok := ev.Event.(*replication.QueryEvent); ok { 182 targetEvent = ev 183 break 184 } 185 targetEventStart = ev.Header.LogPos 186 } 187 finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir) 188 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 189 require.Nil(t, err) 190 require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position) 191 require.Equal(t, "", location.GTIDSetStr()) 192 require.Equal(t, InRangeBinlogPos, posType) 193 } 194 } 195 196 func TestMySQL57NoGTID(t *testing.T) { 197 t.Parallel() 198 flavor := "mysql" 199 relayDir := t.TempDir() 200 beforeTime := time.Now() 201 latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1" 202 203 generator, _ := event.NewGeneratorV2(flavor, "5.7.0", latestGTIDStr, false) 204 205 file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002") 206 require.Equal(t, 15, len(file1Events)) 207 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644) 208 file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003") 209 require.Equal(t, 15, len(file2Events)) 210 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644) 211 file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004") 212 require.Equal(t, 15, len(file3Events)) 213 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644) 214 215 tcctx := tcontext.NewContext(context.Background(), log.L()) 216 { 217 var targetEventStart uint32 218 var targetEvent *replication.BinlogEvent 219 cnt := 0 220 for _, ev := range file3Events { 221 if ev.Header.EventType == replication.ANONYMOUS_GTID_EVENT { 222 targetEvent = ev 223 // second GTID event 224 cnt++ 225 if cnt == 2 { 226 break 227 } 228 } 229 targetEventStart = ev.Header.LogPos 230 } 231 finder := NewLocalBinlogPosFinder(tcctx, false, flavor, relayDir) 232 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 233 require.Nil(t, err) 234 require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position) 235 require.Equal(t, "", location.GTIDSetStr()) 236 require.Equal(t, InRangeBinlogPos, posType) 237 } 238 } 239 240 func TestErrorCase(t *testing.T) { 241 t.Parallel() 242 flavor := "mysql" 243 relayDir := t.TempDir() 244 beforeTime := time.Now() 245 tcctx := tcontext.NewContext(context.Background(), log.L()) 246 { 247 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir+"not-exist") 248 _, _, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix()) 249 require.Regexp(t, ".*no such file or directory.*", err.Error()) 250 } 251 { 252 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, t.TempDir()) 253 _, _, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix()) 254 require.Regexp(t, ".*cannot find binlog files.*", err.Error()) 255 } 256 { 257 file, err := os.Create(path.Join(relayDir, "mysql-bin.000001")) 258 require.Nil(t, err) 259 file.Close() 260 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 261 _, _, err = finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix()) 262 require.Equal(t, "EOF", err.Error()) 263 } 264 } 265 266 func TestMySQL57GTID(t *testing.T) { 267 t.Parallel() 268 flavor := "mysql" 269 relayDir := t.TempDir() 270 beforeTime := time.Now() 271 latestGTIDStr := "ffffffff-ffff-ffff-ffff-ffffffffffff:1" 272 273 generator, _ := event.NewGeneratorV2(flavor, "5.7.0", latestGTIDStr, true) 274 275 file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002") 276 require.Equal(t, 15, len(file1Events)) 277 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644) 278 file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003") 279 require.Equal(t, 15, len(file2Events)) 280 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644) 281 file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004") 282 require.Equal(t, 15, len(file3Events)) 283 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644) 284 285 tcctx := tcontext.NewContext(context.Background(), log.L()) 286 287 { 288 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 289 location, posType, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix()) 290 require.Nil(t, err) 291 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: 4}, location.Position) 292 require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1", location.GTIDSetStr()) 293 require.Equal(t, BelowLowerBoundBinlogPos, posType) 294 } 295 { 296 gtids := []string{ 297 "ffffffff-ffff-ffff-ffff-ffffffffffff:1", 298 "ffffffff-ffff-ffff-ffff-ffffffffffff:1-2", 299 "ffffffff-ffff-ffff-ffff-ffffffffffff:1-3", 300 } 301 var targetEventStart uint32 302 var targetEvent *replication.BinlogEvent 303 cnt := 0 304 for _, ev := range file1Events { 305 if ev.Header.EventType == replication.GTID_EVENT { 306 targetEvent = ev 307 308 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 309 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 310 require.Nil(t, err) 311 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position) 312 require.Equal(t, gtids[cnt], location.GTIDSetStr()) 313 require.Equal(t, InRangeBinlogPos, posType) 314 315 cnt++ 316 } 317 targetEventStart = ev.Header.LogPos 318 } 319 } 320 { 321 targetEventStart := file2Events[len(file2Events)-1].Header.LogPos 322 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 323 location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp)) 324 require.Nil(t, err) 325 require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position) 326 require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1-7", location.GTIDSetStr()) 327 require.Equal(t, InRangeBinlogPos, posType) 328 } 329 { 330 var targetEventStart uint32 331 var targetEvent *replication.BinlogEvent 332 cnt := 0 333 for _, ev := range file3Events { 334 if ev.Header.EventType == replication.GTID_EVENT { 335 targetEvent = ev 336 // third GTID event 337 cnt++ 338 if cnt == 3 { 339 break 340 } 341 } 342 targetEventStart = ev.Header.LogPos 343 } 344 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 345 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 346 require.Nil(t, err) 347 require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position) 348 require.Equal(t, "ffffffff-ffff-ffff-ffff-ffffffffffff:1-9", location.GTIDSetStr()) 349 require.Equal(t, InRangeBinlogPos, posType) 350 } 351 { 352 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 353 location, posType, err := finder.FindByTimestamp(beforeTime.Add(+time.Minute).Unix()) 354 355 require.Nil(t, err) 356 require.Nil(t, location) 357 require.Equal(t, AboveUpperBoundBinlogPos, posType) 358 } 359 } 360 361 func TestMariadbGTID(t *testing.T) { 362 t.Parallel() 363 flavor := "mariadb" 364 relayDir := t.TempDir() 365 beforeTime := time.Now() 366 latestGTIDStr := "1-1-1" 367 368 generator, _ := event.NewGeneratorV2(flavor, "10.0.2", latestGTIDStr, true) 369 370 file1Events, data := genBinlogFile(generator, beforeTime, "mysql-bin.000002") 371 require.Equal(t, 15, len(file1Events)) 372 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000001"), data, 0o644) 373 file2Events, data := genBinlogFile(generator, beforeTime.Add(5*time.Second), "mysql-bin.000003") 374 require.Equal(t, 15, len(file2Events)) 375 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000002"), data, 0o644) 376 file3Events, data := genBinlogFile(generator, beforeTime.Add(10*time.Second), "mysql-bin.000004") 377 require.Equal(t, 15, len(file3Events)) 378 _ = os.WriteFile(path.Join(relayDir, "mysql-bin.000003"), data, 0o644) 379 380 tcctx := tcontext.NewContext(context.Background(), log.L()) 381 382 { 383 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 384 location, posType, err := finder.FindByTimestamp(beforeTime.Add(-time.Minute).Unix()) 385 require.Nil(t, err) 386 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: 4}, location.Position) 387 require.Equal(t, "1-1-1", location.GTIDSetStr()) 388 require.Equal(t, BelowLowerBoundBinlogPos, posType) 389 } 390 { 391 var targetEventStart uint32 392 var targetEvent *replication.BinlogEvent 393 cnt := 0 394 for _, ev := range file1Events { 395 if ev.Header.EventType == replication.MARIADB_GTID_EVENT { 396 targetEvent = ev 397 // second GTID event 398 cnt++ 399 if cnt == 2 { 400 break 401 } 402 } 403 targetEventStart = ev.Header.LogPos 404 } 405 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 406 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 407 require.Nil(t, err) 408 require.Equal(t, mysql.Position{Name: "mysql-bin.000001", Pos: targetEventStart}, location.Position) 409 require.Equal(t, "1-1-2", location.GTIDSetStr()) 410 require.Equal(t, InRangeBinlogPos, posType) 411 } 412 { 413 targetEventStart := file2Events[len(file2Events)-1].Header.LogPos 414 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 415 location, posType, err := finder.FindByTimestamp(int64(file3Events[0].Header.Timestamp)) 416 require.Nil(t, err) 417 require.Equal(t, mysql.Position{Name: "mysql-bin.000002", Pos: targetEventStart}, location.Position) 418 require.Equal(t, "1-1-7", location.GTIDSetStr()) 419 require.Equal(t, InRangeBinlogPos, posType) 420 } 421 { 422 var targetEventStart uint32 423 var targetEvent *replication.BinlogEvent 424 cnt := 0 425 for _, ev := range file3Events { 426 if ev.Header.EventType == replication.MARIADB_GTID_EVENT { 427 targetEvent = ev 428 // second GTID event 429 cnt++ 430 if cnt == 2 { 431 break 432 } 433 } 434 targetEventStart = ev.Header.LogPos 435 } 436 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 437 location, posType, err := finder.FindByTimestamp(int64(targetEvent.Header.Timestamp)) 438 require.Nil(t, err) 439 require.Equal(t, mysql.Position{Name: "mysql-bin.000003", Pos: targetEventStart}, location.Position) 440 require.Equal(t, "1-1-8", location.GTIDSetStr()) 441 require.Equal(t, InRangeBinlogPos, posType) 442 } 443 { 444 finder := NewLocalBinlogPosFinder(tcctx, true, flavor, relayDir) 445 location, posType, err := finder.FindByTimestamp(beforeTime.Add(+time.Minute).Unix()) 446 require.Nil(t, err) 447 require.Nil(t, location) 448 require.Equal(t, AboveUpperBoundBinlogPos, posType) 449 } 450 }