github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/model/sink_test.go (about) 1 // Copyright 2020 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 model 15 16 import ( 17 "sort" 18 "testing" 19 20 timodel "github.com/pingcap/tidb/pkg/parser/model" 21 "github.com/pingcap/tidb/pkg/parser/mysql" 22 "github.com/pingcap/tidb/pkg/parser/types" 23 "github.com/pingcap/tiflow/pkg/sink" 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 ) 27 28 func TestSetFlag(t *testing.T) { 29 t.Parallel() 30 31 var flag ColumnFlagType 32 flag.SetIsBinary() 33 flag.SetIsGeneratedColumn() 34 require.True(t, flag.IsBinary()) 35 require.False(t, flag.IsHandleKey()) 36 require.True(t, flag.IsGeneratedColumn()) 37 flag.UnsetIsBinary() 38 require.False(t, flag.IsBinary()) 39 flag.SetIsMultipleKey() 40 flag.SetIsUniqueKey() 41 require.True(t, flag.IsMultipleKey() && flag.IsUniqueKey()) 42 flag.UnsetIsUniqueKey() 43 flag.UnsetIsGeneratedColumn() 44 flag.UnsetIsMultipleKey() 45 require.False(t, flag.IsUniqueKey() || flag.IsGeneratedColumn() || flag.IsMultipleKey()) 46 47 flag = ColumnFlagType(0) 48 flag.SetIsHandleKey() 49 flag.SetIsPrimaryKey() 50 flag.SetIsUnsigned() 51 require.True(t, flag.IsHandleKey() && flag.IsPrimaryKey() && flag.IsUnsigned()) 52 flag.UnsetIsHandleKey() 53 flag.UnsetIsPrimaryKey() 54 flag.UnsetIsUnsigned() 55 require.False(t, flag.IsHandleKey() || flag.IsPrimaryKey() || flag.IsUnsigned()) 56 flag.SetIsNullable() 57 require.True(t, flag.IsNullable()) 58 flag.UnsetIsNullable() 59 require.False(t, flag.IsNullable()) 60 } 61 62 func TestFlagValue(t *testing.T) { 63 t.Parallel() 64 65 require.Equal(t, ColumnFlagType(0b1), BinaryFlag) 66 require.Equal(t, ColumnFlagType(0b1), BinaryFlag) 67 require.Equal(t, ColumnFlagType(0b10), HandleKeyFlag) 68 require.Equal(t, ColumnFlagType(0b100), GeneratedColumnFlag) 69 require.Equal(t, ColumnFlagType(0b1000), PrimaryKeyFlag) 70 require.Equal(t, ColumnFlagType(0b10000), UniqueKeyFlag) 71 require.Equal(t, ColumnFlagType(0b100000), MultipleKeyFlag) 72 require.Equal(t, ColumnFlagType(0b1000000), NullableFlag) 73 } 74 75 func TestTableNameFuncs(t *testing.T) { 76 t.Parallel() 77 tbl := &TableName{ 78 Schema: "test", 79 Table: "t1", 80 TableID: 1071, 81 } 82 require.Equal(t, "test.t1", tbl.String()) 83 require.Equal(t, "`test`.`t1`", tbl.QuoteString()) 84 require.Equal(t, "test", tbl.GetSchema()) 85 require.Equal(t, "t1", tbl.GetTable()) 86 require.Equal(t, int64(1071), tbl.GetTableID()) 87 } 88 89 func TestRowChangedEventFuncs(t *testing.T) { 90 t.Parallel() 91 deleteRow := &RowChangedEvent{ 92 TableInfo: &TableInfo{ 93 TableName: TableName{ 94 Schema: "test", 95 Table: "t1", 96 }, 97 }, 98 PreColumns: []*ColumnData{ 99 { 100 ColumnID: 1, 101 Value: 1, 102 }, { 103 ColumnID: 2, 104 Value: 2, 105 }, 106 }, 107 } 108 require.True(t, deleteRow.IsDelete()) 109 } 110 111 func TestColumnValueString(t *testing.T) { 112 t.Parallel() 113 testCases := []struct { 114 val interface{} 115 expected string 116 }{ 117 {interface{}(nil), "null"}, 118 {interface{}(true), "1"}, 119 {interface{}(false), "0"}, 120 {interface{}(123), "123"}, 121 {interface{}(int8(-123)), "-123"}, 122 {interface{}(int16(-123)), "-123"}, 123 {interface{}(int32(-123)), "-123"}, 124 {interface{}(int64(-123)), "-123"}, 125 {interface{}(uint8(123)), "123"}, 126 {interface{}(uint16(123)), "123"}, 127 {interface{}(uint32(123)), "123"}, 128 {interface{}(uint64(123)), "123"}, 129 {interface{}(float32(123.01)), "123.01"}, 130 {interface{}(float64(123.01)), "123.01"}, 131 {interface{}("123.01"), "123.01"}, 132 {interface{}([]byte("123.01")), "123.01"}, 133 {interface{}(complex(1, 2)), "(1+2i)"}, 134 } 135 for _, tc := range testCases { 136 s := ColumnValueString(tc.val) 137 require.Equal(t, tc.expected, s) 138 } 139 } 140 141 func TestDDLEventFromJob(t *testing.T) { 142 t.Parallel() 143 ft := types.NewFieldType(mysql.TypeUnspecified) 144 ft.SetFlag(mysql.PriKeyFlag) 145 job := &timodel.Job{ 146 ID: 1071, 147 TableID: 49, 148 SchemaName: "test", 149 Type: timodel.ActionAddColumn, 150 StartTS: 420536581131337731, 151 Query: "alter table t1 add column a int", 152 BinlogInfo: &timodel.HistoryInfo{ 153 TableInfo: &timodel.TableInfo{ 154 ID: 49, 155 Name: timodel.CIStr{O: "t1"}, 156 Columns: []*timodel.ColumnInfo{ 157 {ID: 1, Name: timodel.CIStr{O: "id"}, FieldType: *ft, State: timodel.StatePublic}, 158 {ID: 2, Name: timodel.CIStr{O: "a"}, FieldType: types.FieldType{}, State: timodel.StatePublic}, 159 }, 160 }, 161 FinishedTS: 420536581196873729, 162 }, 163 } 164 preTableInfo := &TableInfo{ 165 TableName: TableName{ 166 Schema: "test", 167 Table: "t1", 168 TableID: 49, 169 }, 170 TableInfo: &timodel.TableInfo{ 171 ID: 49, 172 Name: timodel.CIStr{O: "t1"}, 173 Columns: []*timodel.ColumnInfo{ 174 {ID: 1, Name: timodel.CIStr{O: "id"}, FieldType: *ft, State: timodel.StatePublic}, 175 }, 176 }, 177 } 178 tableInfo := WrapTableInfo(job.SchemaID, job.SchemaName, job.BinlogInfo.FinishedTS, job.BinlogInfo.TableInfo) 179 event := &DDLEvent{} 180 event.FromJob(job, preTableInfo, tableInfo) 181 require.Equal(t, uint64(420536581131337731), event.StartTs) 182 require.Equal(t, int64(49), event.TableInfo.TableName.TableID) 183 require.Equal(t, 1, len(event.PreTableInfo.TableInfo.Columns)) 184 185 event = &DDLEvent{} 186 event.FromJob(job, nil, nil) 187 require.Nil(t, event.PreTableInfo) 188 } 189 190 func TestRenameTables(t *testing.T) { 191 ft := types.NewFieldType(mysql.TypeUnspecified) 192 ft.SetFlag(mysql.PriKeyFlag | mysql.UniqueFlag) 193 job := &timodel.Job{ 194 ID: 71, 195 TableID: 69, 196 SchemaName: "test1", 197 Type: timodel.ActionRenameTables, 198 StartTS: 432853521879007233, 199 Query: "rename table test1.t1 to test1.t10, test1.t2 to test1.t20", 200 BinlogInfo: &timodel.HistoryInfo{ 201 FinishedTS: 432853521879007238, 202 MultipleTableInfos: []*timodel.TableInfo{ 203 { 204 ID: 67, 205 Name: timodel.CIStr{O: "t10"}, 206 Columns: []*timodel.ColumnInfo{ 207 { 208 ID: 1, 209 Name: timodel.CIStr{O: "id"}, 210 FieldType: *ft, 211 State: timodel.StatePublic, 212 }, 213 }, 214 }, 215 { 216 ID: 69, 217 Name: timodel.CIStr{O: "t20"}, 218 Columns: []*timodel.ColumnInfo{ 219 { 220 ID: 1, 221 Name: timodel.CIStr{O: "id"}, 222 FieldType: *ft, 223 State: timodel.StatePublic, 224 }, 225 }, 226 }, 227 }, 228 }, 229 } 230 231 preTableInfo := &TableInfo{ 232 TableName: TableName{ 233 Schema: "test1", 234 Table: "t1", 235 TableID: 67, 236 }, 237 TableInfo: &timodel.TableInfo{ 238 ID: 67, 239 Name: timodel.CIStr{O: "t1"}, 240 Columns: []*timodel.ColumnInfo{ 241 { 242 ID: 1, 243 Name: timodel.CIStr{O: "id"}, 244 FieldType: *ft, 245 State: timodel.StatePublic, 246 }, 247 }, 248 }, 249 } 250 251 tableInfo := &TableInfo{ 252 TableName: TableName{ 253 Schema: "test1", 254 Table: "t10", 255 TableID: 67, 256 }, 257 TableInfo: &timodel.TableInfo{ 258 ID: 67, 259 Name: timodel.CIStr{O: "t10"}, 260 Columns: []*timodel.ColumnInfo{ 261 { 262 ID: 1, 263 Name: timodel.CIStr{O: "id"}, 264 FieldType: *ft, 265 State: timodel.StatePublic, 266 }, 267 }, 268 }, 269 } 270 271 event := &DDLEvent{} 272 event.FromJobWithArgs(job, preTableInfo, tableInfo, "test1", "test1") 273 require.Equal(t, event.PreTableInfo.TableName.TableID, int64(67)) 274 require.Equal(t, event.PreTableInfo.TableName.Table, "t1") 275 require.Len(t, event.PreTableInfo.TableInfo.Columns, 1) 276 require.Equal(t, event.TableInfo.TableName.TableID, int64(67)) 277 require.Equal(t, event.TableInfo.TableName.Table, "t10") 278 require.Len(t, event.TableInfo.TableInfo.Columns, 1) 279 require.Equal(t, event.Query, "RENAME TABLE `test1`.`t1` TO `test1`.`t10`") 280 require.Equal(t, event.Type, timodel.ActionRenameTable) 281 282 preTableInfo = &TableInfo{ 283 TableName: TableName{ 284 Schema: "test1", 285 Table: "t2", 286 TableID: 69, 287 }, 288 TableInfo: &timodel.TableInfo{ 289 ID: 69, 290 Name: timodel.CIStr{O: "t2"}, 291 Columns: []*timodel.ColumnInfo{ 292 { 293 ID: 1, 294 Name: timodel.CIStr{O: "id"}, 295 FieldType: *ft, 296 State: timodel.StatePublic, 297 }, 298 }, 299 }, 300 } 301 302 tableInfo = &TableInfo{ 303 TableName: TableName{ 304 Schema: "test1", 305 Table: "t20", 306 TableID: 69, 307 }, 308 TableInfo: &timodel.TableInfo{ 309 ID: 69, 310 Name: timodel.CIStr{O: "t20"}, 311 Columns: []*timodel.ColumnInfo{ 312 { 313 ID: 1, 314 Name: timodel.CIStr{O: "id"}, 315 FieldType: *ft, 316 State: timodel.StatePublic, 317 }, 318 }, 319 }, 320 } 321 322 event = &DDLEvent{} 323 event.FromJobWithArgs(job, preTableInfo, tableInfo, "test1", "test1") 324 require.Equal(t, event.PreTableInfo.TableName.TableID, int64(69)) 325 require.Equal(t, event.PreTableInfo.TableName.Table, "t2") 326 require.Len(t, event.PreTableInfo.TableInfo.Columns, 1) 327 require.Equal(t, event.TableInfo.TableName.TableID, int64(69)) 328 require.Equal(t, event.TableInfo.TableName.Table, "t20") 329 require.Len(t, event.TableInfo.TableInfo.Columns, 1) 330 require.Equal(t, event.Query, "RENAME TABLE `test1`.`t2` TO `test1`.`t20`") 331 require.Equal(t, event.Type, timodel.ActionRenameTable) 332 } 333 334 func TestExchangeTablePartition(t *testing.T) { 335 ft := types.NewFieldType(mysql.TypeUnspecified) 336 ft.SetFlag(mysql.PriKeyFlag | mysql.UniqueFlag) 337 job := &timodel.Job{ 338 ID: 71, 339 TableID: 69, 340 SchemaName: "test1", 341 Type: timodel.ActionExchangeTablePartition, 342 StartTS: 432853521879007233, 343 Query: "alter table t1 exchange partition p0 with table t2", 344 BinlogInfo: &timodel.HistoryInfo{ 345 FinishedTS: 432853521879007238, 346 }, 347 } 348 349 // source table 350 preTableInfo := &TableInfo{ 351 TableName: TableName{ 352 Schema: "test2", 353 Table: "t2", 354 TableID: 67, 355 }, 356 TableInfo: &timodel.TableInfo{ 357 ID: 67, 358 Name: timodel.CIStr{O: "t1"}, 359 Columns: []*timodel.ColumnInfo{ 360 { 361 ID: 1, 362 Name: timodel.CIStr{O: "id"}, 363 FieldType: *ft, 364 State: timodel.StatePublic, 365 }, 366 }, 367 }, 368 } 369 370 // target table 371 tableInfo := &TableInfo{ 372 TableName: TableName{ 373 Schema: "test1", 374 Table: "t1", 375 TableID: 69, 376 }, 377 TableInfo: &timodel.TableInfo{ 378 ID: 69, 379 Name: timodel.CIStr{O: "t10"}, 380 Columns: []*timodel.ColumnInfo{ 381 { 382 ID: 1, 383 Name: timodel.CIStr{O: "id"}, 384 FieldType: *ft, 385 State: timodel.StatePublic, 386 }, 387 }, 388 }, 389 } 390 391 event := &DDLEvent{} 392 event.FromJob(job, preTableInfo, tableInfo) 393 require.Equal(t, event.PreTableInfo.TableName.TableID, int64(67)) 394 require.Equal(t, event.PreTableInfo.TableName.Table, "t2") 395 require.Len(t, event.PreTableInfo.TableInfo.Columns, 1) 396 require.Equal(t, event.TableInfo.TableName.TableID, int64(69)) 397 require.Equal(t, event.TableInfo.TableName.Table, "t1") 398 require.Len(t, event.TableInfo.TableInfo.Columns, 1) 399 require.Equal(t, "ALTER TABLE `test1`.`t1` EXCHANGE PARTITION `p0` WITH TABLE `test2`.`t2`", event.Query) 400 require.Equal(t, event.Type, timodel.ActionExchangeTablePartition) 401 } 402 403 func TestSortRowChangedEvent(t *testing.T) { 404 events := []*RowChangedEvent{ 405 { 406 PreColumns: []*ColumnData{{}}, 407 Columns: []*ColumnData{{}}, 408 }, 409 { 410 Columns: []*ColumnData{{}}, 411 }, 412 { 413 PreColumns: []*ColumnData{{}}, 414 }, 415 } 416 assert.True(t, events[0].IsUpdate()) 417 assert.True(t, events[1].IsInsert()) 418 assert.True(t, events[2].IsDelete()) 419 sort.Sort(txnRows(events)) 420 assert.True(t, events[0].IsDelete()) 421 assert.True(t, events[1].IsUpdate()) 422 assert.True(t, events[2].IsInsert()) 423 } 424 425 func TestTrySplitAndSortUpdateEventNil(t *testing.T) { 426 t.Parallel() 427 428 events := []*RowChangedEvent{nil} 429 result, err := trySplitAndSortUpdateEvent(events) 430 require.NoError(t, err) 431 require.Equal(t, 0, len(result)) 432 } 433 434 func TestTrySplitAndSortUpdateEventEmpty(t *testing.T) { 435 t.Parallel() 436 437 events := []*RowChangedEvent{ 438 { 439 StartTs: 1, 440 CommitTs: 2, 441 }, 442 } 443 result, err := trySplitAndSortUpdateEvent(events) 444 require.NoError(t, err) 445 require.Equal(t, 0, len(result)) 446 } 447 448 func TestTrySplitAndSortUpdateEvent(t *testing.T) { 449 t.Parallel() 450 451 // Update primary key. 452 tableInfoWithPrimaryKey := BuildTableInfo("test", "t", []*Column{ 453 { 454 Name: "col1", 455 Flag: BinaryFlag, 456 }, 457 { 458 Name: "col2", 459 Flag: HandleKeyFlag | PrimaryKeyFlag, 460 }, 461 }, [][]int{{1}}) 462 events := []*RowChangedEvent{ 463 { 464 CommitTs: 1, 465 TableInfo: tableInfoWithPrimaryKey, 466 Columns: Columns2ColumnDatas([]*Column{ 467 { 468 Name: "col1", 469 Flag: BinaryFlag, 470 Value: "col1-value-updated", 471 }, 472 { 473 Name: "col2", 474 Flag: HandleKeyFlag | PrimaryKeyFlag, 475 Value: "col2-value-updated", 476 }, 477 }, tableInfoWithPrimaryKey), 478 PreColumns: Columns2ColumnDatas([]*Column{ 479 { 480 Name: "col1", 481 Value: "col1-value", 482 }, 483 { 484 Name: "col2", 485 Value: "col2-value", 486 }, 487 }, tableInfoWithPrimaryKey), 488 }, 489 } 490 result, err := trySplitAndSortUpdateEvent(events) 491 require.NoError(t, err) 492 require.Equal(t, 2, len(result)) 493 require.True(t, result[0].IsDelete()) 494 require.True(t, result[1].IsInsert()) 495 496 // Update unique key. 497 tableInfoWithUniqueKey := BuildTableInfo("test", "t", []*Column{ 498 { 499 Name: "col1", 500 Flag: BinaryFlag, 501 }, 502 { 503 Name: "col2", 504 Flag: UniqueKeyFlag | NullableFlag, 505 }, 506 }, [][]int{{1}}) 507 events = []*RowChangedEvent{ 508 { 509 CommitTs: 1, 510 TableInfo: tableInfoWithUniqueKey, 511 Columns: Columns2ColumnDatas([]*Column{ 512 { 513 Name: "col1", 514 Value: "col1-value-updated", 515 }, 516 { 517 Name: "col2", 518 Value: "col2-value-updated", 519 }, 520 }, tableInfoWithUniqueKey), 521 PreColumns: Columns2ColumnDatas([]*Column{ 522 { 523 Name: "col1", 524 Value: "col1-value", 525 }, 526 { 527 Name: "col2", 528 Value: "col2-value", 529 }, 530 }, tableInfoWithUniqueKey), 531 }, 532 } 533 result, err = trySplitAndSortUpdateEvent(events) 534 require.NoError(t, err) 535 require.Equal(t, 2, len(result)) 536 require.True(t, result[0].IsDelete()) 537 require.True(t, result[0].IsDelete()) 538 require.True(t, result[1].IsInsert()) 539 540 // Update non-handle key. 541 events = []*RowChangedEvent{ 542 { 543 CommitTs: 1, 544 TableInfo: tableInfoWithPrimaryKey, 545 Columns: Columns2ColumnDatas([]*Column{ 546 { 547 Name: "col1", 548 Value: "col1-value-updated", 549 }, 550 { 551 Name: "col2", 552 Value: "col2-value", 553 }, 554 }, tableInfoWithPrimaryKey), 555 PreColumns: Columns2ColumnDatas([]*Column{ 556 { 557 Name: "col1", 558 Value: "col1-value", 559 }, 560 { 561 Name: "col2", 562 Value: "col2-value", 563 }, 564 }, tableInfoWithPrimaryKey), 565 }, 566 } 567 result, err = trySplitAndSortUpdateEvent(events) 568 require.NoError(t, err) 569 require.Equal(t, 1, len(result)) 570 } 571 572 func TestTxnTrySplitAndSortUpdateEvent(t *testing.T) { 573 columns := []*Column{ 574 { 575 Name: "col1", 576 Flag: BinaryFlag, 577 Value: "col1-value", 578 }, 579 { 580 Name: "col2", 581 Flag: HandleKeyFlag | UniqueKeyFlag | PrimaryKeyFlag, 582 Value: "col2-value-updated", 583 }, 584 } 585 preColumns := []*Column{ 586 { 587 Name: "col1", 588 Flag: BinaryFlag, 589 Value: "col1-value", 590 }, 591 { 592 Name: "col2", 593 Flag: HandleKeyFlag | UniqueKeyFlag | PrimaryKeyFlag, 594 Value: "col2-value", 595 }, 596 } 597 tableInfo := BuildTableInfo("test", "t", columns, [][]int{{1}}) 598 ukUpdatedEvent := &RowChangedEvent{ 599 TableInfo: tableInfo, 600 PreColumns: Columns2ColumnDatas(preColumns, tableInfo), 601 Columns: Columns2ColumnDatas(columns, tableInfo), 602 } 603 txn := &SingleTableTxn{ 604 Rows: []*RowChangedEvent{ukUpdatedEvent}, 605 } 606 607 err := txn.TrySplitAndSortUpdateEvent(sink.KafkaScheme) 608 require.NoError(t, err) 609 require.Len(t, txn.Rows, 2) 610 611 txn = &SingleTableTxn{ 612 Rows: []*RowChangedEvent{ukUpdatedEvent}, 613 } 614 err = txn.TrySplitAndSortUpdateEvent(sink.MySQLScheme) 615 require.NoError(t, err) 616 require.Len(t, txn.Rows, 1) 617 618 txn2 := &SingleTableTxn{ 619 Rows: []*RowChangedEvent{ukUpdatedEvent, ukUpdatedEvent}, 620 } 621 err = txn.TrySplitAndSortUpdateEvent(sink.MySQLScheme) 622 require.NoError(t, err) 623 require.Len(t, txn2.Rows, 2) 624 } 625 626 func TestToRedoLog(t *testing.T) { 627 cols := []*Column{ 628 { 629 Name: "col1", 630 Flag: BinaryFlag, 631 }, 632 { 633 Name: "col2", 634 Flag: HandleKeyFlag | UniqueKeyFlag, 635 }, 636 } 637 tableInfo := BuildTableInfo("test", "t", cols, [][]int{{1}}) 638 event := &RowChangedEvent{ 639 StartTs: 100, 640 CommitTs: 1000, 641 PhysicalTableID: 1, 642 TableInfo: tableInfo, 643 Columns: Columns2ColumnDatas([]*Column{ 644 { 645 Name: "col1", 646 Value: "col1-value", 647 }, 648 { 649 Name: "col2", 650 Value: "col2-value-updated", 651 }, 652 }, tableInfo), 653 } 654 eventInRedoLog := event.ToRedoLog() 655 require.Equal(t, event.StartTs, eventInRedoLog.RedoRow.Row.StartTs) 656 require.Equal(t, event.CommitTs, eventInRedoLog.RedoRow.Row.CommitTs) 657 require.Equal(t, event.PhysicalTableID, eventInRedoLog.RedoRow.Row.Table.TableID) 658 require.Equal(t, event.TableInfo.GetSchemaName(), eventInRedoLog.RedoRow.Row.Table.Schema) 659 require.Equal(t, event.TableInfo.GetTableName(), eventInRedoLog.RedoRow.Row.Table.Table) 660 require.Equal(t, event.Columns, Columns2ColumnDatas(eventInRedoLog.RedoRow.Row.Columns, tableInfo)) 661 }