github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/mounter_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 entry 15 16 import ( 17 "bytes" 18 "context" 19 "math" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/pingcap/log" 25 ticonfig "github.com/pingcap/tidb/pkg/config" 26 "github.com/pingcap/tidb/pkg/ddl" 27 "github.com/pingcap/tidb/pkg/executor" 28 tidbkv "github.com/pingcap/tidb/pkg/kv" 29 "github.com/pingcap/tidb/pkg/meta/autoid" 30 "github.com/pingcap/tidb/pkg/parser" 31 "github.com/pingcap/tidb/pkg/parser/ast" 32 timodel "github.com/pingcap/tidb/pkg/parser/model" 33 "github.com/pingcap/tidb/pkg/parser/mysql" 34 "github.com/pingcap/tidb/pkg/session" 35 "github.com/pingcap/tidb/pkg/store/mockstore" 36 "github.com/pingcap/tidb/pkg/testkit" 37 "github.com/pingcap/tidb/pkg/types" 38 "github.com/pingcap/tidb/pkg/util/mock" 39 "github.com/pingcap/tiflow/cdc/model" 40 "github.com/pingcap/tiflow/pkg/config" 41 cerror "github.com/pingcap/tiflow/pkg/errors" 42 "github.com/pingcap/tiflow/pkg/filter" 43 "github.com/pingcap/tiflow/pkg/integrity" 44 "github.com/pingcap/tiflow/pkg/sink/codec/avro" 45 codecCommon "github.com/pingcap/tiflow/pkg/sink/codec/common" 46 "github.com/pingcap/tiflow/pkg/spanz" 47 "github.com/pingcap/tiflow/pkg/sqlmodel" 48 "github.com/pingcap/tiflow/pkg/util" 49 "github.com/stretchr/testify/require" 50 "github.com/tikv/client-go/v2/oracle" 51 "go.uber.org/zap" 52 ) 53 54 var dummyChangeFeedID = model.DefaultChangeFeedID("dummy_changefeed") 55 56 func TestMounterDisableOldValue(t *testing.T) { 57 testCases := []struct { 58 tableName string 59 createTableDDL string 60 // [] for rows, []interface{} for columns. 61 values [][]interface{} 62 // [] for table partition if there is any, 63 // []int for approximateBytes of rows. 64 putApproximateBytes [][]int 65 delApproximateBytes [][]int 66 }{{ 67 tableName: "simple", 68 createTableDDL: "create table simple(id int primary key)", 69 values: [][]interface{}{{1}, {2}, {3}, {4}, {5}}, 70 putApproximateBytes: [][]int{{346, 346, 346, 346, 346}}, 71 delApproximateBytes: [][]int{{346, 346, 346, 346, 346}}, 72 }, { 73 tableName: "no_pk", 74 createTableDDL: "create table no_pk(id int not null unique key)", 75 values: [][]interface{}{{1}, {2}, {3}, {4}, {5}}, 76 putApproximateBytes: [][]int{{345, 345, 345, 345, 345}}, 77 delApproximateBytes: [][]int{{217, 217, 217, 217, 217}}, 78 }, { 79 tableName: "many_index", 80 createTableDDL: "create table many_index(id int not null unique key, c1 int unique key, c2 int, INDEX (c2))", 81 values: [][]interface{}{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}}, 82 putApproximateBytes: [][]int{{638, 638, 638, 638, 638}}, 83 delApproximateBytes: [][]int{{254, 254, 254, 254, 254}}, 84 }, { 85 tableName: "default_value", 86 createTableDDL: "create table default_value(id int primary key, c1 int, c2 int not null default 5, c3 varchar(20), c4 varchar(20) not null default '666')", 87 values: [][]interface{}{{1}, {2}, {3}, {4}, {5}}, 88 putApproximateBytes: [][]int{{676, 676, 676, 676, 676}}, 89 delApproximateBytes: [][]int{{353, 353, 353, 353, 353}}, 90 }, { 91 tableName: "partition_table", 92 createTableDDL: `CREATE TABLE partition_table ( 93 id INT NOT NULL AUTO_INCREMENT UNIQUE KEY, 94 fname VARCHAR(25) NOT NULL, 95 lname VARCHAR(25) NOT NULL, 96 store_id INT NOT NULL, 97 department_id INT NOT NULL, 98 INDEX (department_id) 99 ) 100 101 PARTITION BY RANGE(id) ( 102 PARTITION p0 VALUES LESS THAN (5), 103 PARTITION p1 VALUES LESS THAN (10), 104 PARTITION p2 VALUES LESS THAN (15), 105 PARTITION p3 VALUES LESS THAN (20) 106 )`, 107 values: [][]interface{}{ 108 {1, "aa", "bb", 12, 12}, 109 {6, "aac", "bab", 51, 51}, 110 {11, "aad", "bsb", 71, 61}, 111 {18, "aae", "bbf", 21, 14}, 112 {15, "afa", "bbc", 11, 12}, 113 }, 114 putApproximateBytes: [][]int{{775}, {777}, {777}, {777, 777}}, 115 delApproximateBytes: [][]int{{227}, {227}, {227}, {227, 227}}, 116 }, { 117 tableName: "tp_int", 118 createTableDDL: `create table tp_int 119 ( 120 id int auto_increment, 121 c_tinyint tinyint null, 122 c_smallint smallint null, 123 c_mediumint mediumint null, 124 c_int int null, 125 c_bigint bigint null, 126 constraint pk 127 primary key (id) 128 );`, 129 values: [][]interface{}{ 130 {1, 1, 2, 3, 4, 5}, 131 {2}, 132 {3, 3, 4, 5, 6, 7}, 133 {4, 127, 32767, 8388607, 2147483647, 9223372036854775807}, 134 {5, -128, -32768, -8388608, -2147483648, -9223372036854775808}, 135 }, 136 putApproximateBytes: [][]int{{986, 626, 986, 986, 986}}, 137 delApproximateBytes: [][]int{{346, 346, 346, 346, 346}}, 138 }, { 139 tableName: "tp_text", 140 createTableDDL: `create table tp_text 141 ( 142 id int auto_increment, 143 c_tinytext tinytext null, 144 c_text text null, 145 c_mediumtext mediumtext null, 146 c_longtext longtext null, 147 c_varchar varchar(16) null, 148 c_char char(16) null, 149 c_tinyblob tinyblob null, 150 c_blob blob null, 151 c_mediumblob mediumblob null, 152 c_longblob longblob null, 153 c_binary binary(16) null, 154 c_varbinary varbinary(16) null, 155 constraint pk 156 primary key (id) 157 );`, 158 values: [][]interface{}{ 159 {1}, 160 { 161 2, "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", 162 "89504E470D0A1A0A", 163 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 164 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 165 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 166 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 167 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 168 string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}), 169 }, 170 { 171 3, "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", 172 "bug free", "bug free", "bug free", "bug free", 173 }, 174 {4, "", "", "", "", "", "", "", "", "", "", "", ""}, 175 {5, "你好", "我好", "大家好", "道路", "千万条", "安全", "第一条", "行车", "不规范", "亲人", "两行泪", "!"}, 176 {6, "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "☺️", "😊", "😇", "🙂"}, 177 }, 178 putApproximateBytes: [][]int{{1019, 1459, 1411, 1323, 1398, 1369}}, 179 delApproximateBytes: [][]int{{347, 347, 347, 347, 347, 347}}, 180 }, { 181 tableName: "tp_time", 182 createTableDDL: `create table tp_time 183 ( 184 id int auto_increment, 185 c_date date null, 186 c_datetime datetime null, 187 c_timestamp timestamp null, 188 c_time time null, 189 c_year year null, 190 constraint pk 191 primary key (id) 192 );`, 193 values: [][]interface{}{ 194 {1}, 195 {2, "2020-02-20", "2020-02-20 02:20:20", "2020-02-20 02:20:20", "02:20:20", "2020"}, 196 }, 197 putApproximateBytes: [][]int{{627, 819}}, 198 delApproximateBytes: [][]int{{347, 347}}, 199 }, { 200 tableName: "tp_real", 201 createTableDDL: `create table tp_real 202 ( 203 id int auto_increment, 204 c_float float null, 205 c_double double null, 206 c_decimal decimal null, 207 constraint pk 208 primary key (id) 209 );`, 210 values: [][]interface{}{ 211 {1}, 212 {2, "2020.0202", "2020.0303", "2020.0404"}, 213 }, 214 putApproximateBytes: [][]int{{563, 551}}, 215 delApproximateBytes: [][]int{{347, 347}}, 216 }, { 217 tableName: "tp_other", 218 createTableDDL: `create table tp_other 219 ( 220 id int auto_increment, 221 c_enum enum ('a','b','c') null, 222 c_set set ('a','b','c') null, 223 c_bit bit(64) null, 224 c_json json null, 225 constraint pk 226 primary key (id) 227 );`, 228 values: [][]interface{}{ 229 {1}, 230 {2, "a", "a,c", 888, `{"aa":"bb"}`}, 231 }, 232 putApproximateBytes: [][]int{{636, 624}}, 233 delApproximateBytes: [][]int{{348, 348}}, 234 }, { 235 tableName: "clustered_index1", 236 createTableDDL: "CREATE TABLE clustered_index1 (id VARCHAR(255) PRIMARY KEY, data INT);", 237 values: [][]interface{}{ 238 {"hhh"}, 239 {"你好😘", 666}, 240 {"世界🤪", 888}, 241 }, 242 putApproximateBytes: [][]int{{383, 446, 446}}, 243 delApproximateBytes: [][]int{{311, 318, 318}}, 244 }, { 245 tableName: "clustered_index2", 246 createTableDDL: "CREATE TABLE clustered_index2 (id VARCHAR(255), data INT, ddaa date, PRIMARY KEY (id, data, ddaa), UNIQUE KEY (id, data, ddaa));", 247 values: [][]interface{}{ 248 {"你好😘", 666, "2020-11-20"}, 249 {"世界🤪", 888, "2020-05-12"}, 250 }, 251 putApproximateBytes: [][]int{{592, 592}}, 252 delApproximateBytes: [][]int{{592, 592}}, 253 }} 254 for _, tc := range testCases { 255 testMounterDisableOldValue(t, tc) 256 } 257 } 258 259 func testMounterDisableOldValue(t *testing.T, tc struct { 260 tableName string 261 createTableDDL string 262 values [][]interface{} 263 putApproximateBytes [][]int 264 delApproximateBytes [][]int 265 }, 266 ) { 267 store, err := mockstore.NewMockStore() 268 require.Nil(t, err) 269 defer store.Close() //nolint:errcheck 270 ticonfig.UpdateGlobal(func(conf *ticonfig.Config) { 271 // we can update the tidb config here 272 }) 273 session.SetSchemaLease(0) 274 session.DisableStats4Test() 275 domain, err := session.BootstrapSession(store) 276 require.Nil(t, err) 277 defer domain.Close() 278 domain.SetStatsUpdating(true) 279 tk := testkit.NewTestKit(t, store) 280 tk.MustExec("set @@tidb_enable_clustered_index=1;") 281 tk.MustExec("use test;") 282 283 tk.MustExec(tc.createTableDDL) 284 285 f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "") 286 require.Nil(t, err) 287 jobs, err := getAllHistoryDDLJob(store, f) 288 require.Nil(t, err) 289 290 scheamStorage, err := NewSchemaStorage(nil, 0, false, dummyChangeFeedID, util.RoleTester, f) 291 require.Nil(t, err) 292 for _, job := range jobs { 293 err := scheamStorage.HandleDDLJob(job) 294 require.Nil(t, err) 295 } 296 tableInfo, ok := scheamStorage.GetLastSnapshot().TableByName("test", tc.tableName) 297 require.True(t, ok) 298 if tableInfo.IsCommonHandle { 299 // we can check this log to make sure if the clustered-index is enabled 300 log.Info("this table is enable the clustered index", zap.String("tableName", tableInfo.Name.L)) 301 } 302 303 for _, params := range tc.values { 304 305 insertSQL := prepareInsertSQL(t, tableInfo, len(params)) 306 tk.MustExec(insertSQL, params...) 307 } 308 309 ver, err := store.CurrentVersion(oracle.GlobalTxnScope) 310 require.Nil(t, err) 311 scheamStorage.AdvanceResolvedTs(ver.Ver) 312 config := config.GetDefaultReplicaConfig() 313 filter, err := filter.NewFilter(config, "") 314 require.Nil(t, err) 315 mounter := NewMounter(scheamStorage, 316 model.DefaultChangeFeedID("c1"), time.UTC, filter, config.Integrity).(*mounter) 317 mounter.tz = time.Local 318 ctx := context.Background() 319 320 // [TODO] check size and readd rowBytes 321 mountAndCheckRowInTable := func(tableID int64, _ []int, f func(key []byte, value []byte) *model.RawKVEntry) int { 322 var rows int 323 walkTableSpanInStore(t, store, tableID, func(key []byte, value []byte) { 324 rawKV := f(key, value) 325 row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV) 326 require.Nil(t, err) 327 if row == nil { 328 return 329 } 330 rows++ 331 require.Equal(t, row.TableInfo.GetTableName(), tc.tableName) 332 require.Equal(t, row.TableInfo.GetSchemaName(), "test") 333 // [TODO] check size and reopen this check 334 // require.Equal(t, rowBytes[rows-1], row.ApproximateBytes(), row) 335 t.Log("ApproximateBytes", tc.tableName, rows-1, row.ApproximateBytes()) 336 // TODO: test column flag, column type and index columns 337 if len(row.Columns) != 0 { 338 checkSQL, params := prepareCheckSQL(t, tc.tableName, row.GetColumns()) 339 result := tk.MustQuery(checkSQL, params...) 340 result.Check([][]interface{}{{"1"}}) 341 } 342 if len(row.PreColumns) != 0 { 343 checkSQL, params := prepareCheckSQL(t, tc.tableName, row.GetPreColumns()) 344 result := tk.MustQuery(checkSQL, params...) 345 result.Check([][]interface{}{{"1"}}) 346 } 347 }) 348 return rows 349 } 350 mountAndCheckRow := func(rowsBytes [][]int, f func(key []byte, value []byte) *model.RawKVEntry) int { 351 partitionInfo := tableInfo.GetPartitionInfo() 352 if partitionInfo == nil { 353 return mountAndCheckRowInTable(tableInfo.ID, rowsBytes[0], f) 354 } 355 var rows int 356 for i, p := range partitionInfo.Definitions { 357 rows += mountAndCheckRowInTable(p.ID, rowsBytes[i], f) 358 } 359 return rows 360 } 361 362 rows := mountAndCheckRow(tc.putApproximateBytes, func(key []byte, value []byte) *model.RawKVEntry { 363 return &model.RawKVEntry{ 364 OpType: model.OpTypePut, 365 Key: key, 366 Value: value, 367 StartTs: ver.Ver - 1, 368 CRTs: ver.Ver, 369 } 370 }) 371 require.Equal(t, rows, len(tc.values)) 372 373 rows = mountAndCheckRow(tc.delApproximateBytes, func(key []byte, value []byte) *model.RawKVEntry { 374 return &model.RawKVEntry{ 375 OpType: model.OpTypeDelete, 376 Key: key, 377 Value: nil, // delete event doesn't include a value when old-value is disabled 378 StartTs: ver.Ver - 1, 379 CRTs: ver.Ver, 380 } 381 }) 382 require.Equal(t, rows, len(tc.values)) 383 } 384 385 func prepareInsertSQL(t *testing.T, tableInfo *model.TableInfo, columnLens int) string { 386 var sb strings.Builder 387 _, err := sb.WriteString("INSERT INTO " + tableInfo.Name.O + "(") 388 require.Nil(t, err) 389 for i := 0; i < columnLens; i++ { 390 col := tableInfo.Columns[i] 391 if i != 0 { 392 _, err = sb.WriteString(", ") 393 require.Nil(t, err) 394 } 395 _, err = sb.WriteString(col.Name.O) 396 require.Nil(t, err) 397 } 398 _, err = sb.WriteString(") VALUES (") 399 require.Nil(t, err) 400 for i := 0; i < columnLens; i++ { 401 if i != 0 { 402 _, err = sb.WriteString(", ") 403 require.Nil(t, err) 404 } 405 _, err = sb.WriteString("?") 406 require.Nil(t, err) 407 } 408 _, err = sb.WriteString(")") 409 require.Nil(t, err) 410 return sb.String() 411 } 412 413 func prepareCheckSQL(t *testing.T, tableName string, cols []*model.Column) (string, []interface{}) { 414 var sb strings.Builder 415 _, err := sb.WriteString("SELECT count(1) FROM " + tableName + " WHERE ") 416 require.Nil(t, err) 417 params := make([]interface{}, 0, len(cols)) 418 for i, col := range cols { 419 // Since float type has precision problem, so skip it to avoid compare float number. 420 if col == nil || col.Type == mysql.TypeFloat { 421 continue 422 } 423 if i != 0 { 424 _, err = sb.WriteString(" AND ") 425 require.Nil(t, err) 426 } 427 if col.Value == nil { 428 _, err = sb.WriteString(col.Name + " IS NULL") 429 require.Nil(t, err) 430 continue 431 } 432 // convert types for tk.MustQuery 433 if bytes, ok := col.Value.([]byte); ok { 434 col.Value = string(bytes) 435 } 436 params = append(params, col.Value) 437 if col.Type == mysql.TypeJSON { 438 _, err = sb.WriteString(col.Name + " = CAST(? AS JSON)") 439 } else { 440 _, err = sb.WriteString(col.Name + " = ?") 441 } 442 require.Nil(t, err) 443 } 444 return sb.String(), params 445 } 446 447 func walkTableSpanInStore(t *testing.T, store tidbkv.Storage, tableID int64, f func(key []byte, value []byte)) { 448 txn, err := store.Begin() 449 require.Nil(t, err) 450 defer txn.Rollback() //nolint:errcheck 451 startKey, endKey := spanz.GetTableRange(tableID) 452 kvIter, err := txn.Iter(startKey, endKey) 453 require.Nil(t, err) 454 defer kvIter.Close() 455 for kvIter.Valid() { 456 f(kvIter.Key(), kvIter.Value()) 457 err = kvIter.Next() 458 require.Nil(t, err) 459 } 460 } 461 462 func getLastKeyValueInStore(t *testing.T, store tidbkv.Storage, tableID int64) (key, value []byte) { 463 txn, err := store.Begin() 464 require.NoError(t, err) 465 defer txn.Rollback() //nolint:errcheck 466 startKey, endKey := spanz.GetTableRange(tableID) 467 kvIter, err := txn.Iter(startKey, endKey) 468 require.NoError(t, err) 469 defer kvIter.Close() 470 for kvIter.Valid() { 471 key = kvIter.Key() 472 value = kvIter.Value() 473 err = kvIter.Next() 474 require.NoError(t, err) 475 } 476 return key, value 477 } 478 479 // We use OriginDefaultValue instead of DefaultValue in the ut, pls ref to 480 // https://github.com/pingcap/tiflow/issues/4048 481 // Ref: https://github.com/pingcap/tidb/blob/d2c352980a43bb593db81fd1db996f47af596d91/table/column.go#L489 482 func TestGetDefaultZeroValue(t *testing.T) { 483 // Check following MySQL type, ref to: 484 // https://github.com/pingcap/tidb/blob/master/parser/mysql/type.go 485 486 // mysql flag null 487 ftNull := types.NewFieldType(mysql.TypeUnspecified) 488 489 // mysql.TypeTiny + notnull 490 ftTinyIntNotNull := types.NewFieldType(mysql.TypeTiny) 491 ftTinyIntNotNull.AddFlag(mysql.NotNullFlag) 492 493 // mysql.TypeTiny + notnull + unsigned 494 ftTinyIntNotNullUnSigned := types.NewFieldType(mysql.TypeTiny) 495 ftTinyIntNotNullUnSigned.SetFlag(mysql.NotNullFlag) 496 ftTinyIntNotNullUnSigned.AddFlag(mysql.UnsignedFlag) 497 498 // mysql.TypeTiny + null 499 ftTinyIntNull := types.NewFieldType(mysql.TypeTiny) 500 501 // mysql.TypeShort + notnull 502 ftShortNotNull := types.NewFieldType(mysql.TypeShort) 503 ftShortNotNull.SetFlag(mysql.NotNullFlag) 504 505 // mysql.TypeLong + notnull 506 ftLongNotNull := types.NewFieldType(mysql.TypeLong) 507 ftLongNotNull.SetFlag(mysql.NotNullFlag) 508 509 // mysql.TypeLonglong + notnull 510 ftLongLongNotNull := types.NewFieldType(mysql.TypeLonglong) 511 ftLongLongNotNull.SetFlag(mysql.NotNullFlag) 512 513 // mysql.TypeInt24 + notnull 514 ftInt24NotNull := types.NewFieldType(mysql.TypeInt24) 515 ftInt24NotNull.SetFlag(mysql.NotNullFlag) 516 517 // mysql.TypeFloat + notnull 518 ftTypeFloatNotNull := types.NewFieldType(mysql.TypeFloat) 519 ftTypeFloatNotNull.SetFlag(mysql.NotNullFlag) 520 521 // mysql.TypeFloat + notnull + unsigned 522 ftTypeFloatNotNullUnSigned := types.NewFieldType(mysql.TypeFloat) 523 ftTypeFloatNotNullUnSigned.SetFlag(mysql.NotNullFlag | mysql.UnsignedFlag) 524 525 // mysql.TypeFloat + null 526 ftTypeFloatNull := types.NewFieldType(mysql.TypeFloat) 527 528 // mysql.TypeDouble + notnull 529 ftTypeDoubleNotNull := types.NewFieldType(mysql.TypeDouble) 530 ftTypeDoubleNotNull.SetFlag(mysql.NotNullFlag) 531 532 // mysql.TypeNewDecimal + notnull 533 ftTypeNewDecimalNull := types.NewFieldType(mysql.TypeNewDecimal) 534 ftTypeNewDecimalNull.SetFlen(5) 535 ftTypeNewDecimalNull.SetDecimal(2) 536 537 // mysql.TypeNewDecimal + notnull 538 ftTypeNewDecimalNotNull := types.NewFieldType(mysql.TypeNewDecimal) 539 ftTypeNewDecimalNotNull.SetFlag(mysql.NotNullFlag) 540 ftTypeNewDecimalNotNull.SetFlen(5) 541 ftTypeNewDecimalNotNull.SetDecimal(2) 542 543 // mysql.TypeNull 544 ftTypeNull := types.NewFieldType(mysql.TypeNull) 545 546 // mysql.TypeTimestamp + notnull 547 ftTypeTimestampNotNull := types.NewFieldType(mysql.TypeTimestamp) 548 ftTypeTimestampNotNull.SetFlag(mysql.NotNullFlag) 549 550 // mysql.TypeTimestamp + notnull 551 ftTypeTimestampNull := types.NewFieldType(mysql.TypeTimestamp) 552 553 // mysql.TypeDate + notnull 554 ftTypeDateNotNull := types.NewFieldType(mysql.TypeDate) 555 ftTypeDateNotNull.SetFlag(mysql.NotNullFlag) 556 557 // mysql.TypeDuration + notnull 558 ftTypeDurationNotNull := types.NewFieldType(mysql.TypeDuration) 559 ftTypeDurationNotNull.SetFlag(mysql.NotNullFlag) 560 561 // mysql.TypeDatetime + notnull 562 ftTypeDatetimeNotNull := types.NewFieldType(mysql.TypeDatetime) 563 ftTypeDatetimeNotNull.SetFlag(mysql.NotNullFlag) 564 565 // mysql.TypeYear + notnull 566 ftTypeYearNotNull := types.NewFieldType(mysql.TypeYear) 567 ftTypeYearNotNull.SetFlag(mysql.NotNullFlag) 568 569 // mysql.TypeNewDate + notnull 570 ftTypeNewDateNotNull := types.NewFieldType(mysql.TypeNewDate) 571 ftTypeNewDateNotNull.SetFlag(mysql.NotNullFlag) 572 573 // mysql.TypeVarchar + notnull 574 ftTypeVarcharNotNull := types.NewFieldType(mysql.TypeVarchar) 575 ftTypeVarcharNotNull.SetFlag(mysql.NotNullFlag) 576 577 // mysql.TypeTinyBlob + notnull 578 ftTypeTinyBlobNotNull := types.NewFieldType(mysql.TypeTinyBlob) 579 ftTypeTinyBlobNotNull.SetFlag(mysql.NotNullFlag) 580 581 // mysql.TypeMediumBlob + notnull 582 ftTypeMediumBlobNotNull := types.NewFieldType(mysql.TypeMediumBlob) 583 ftTypeMediumBlobNotNull.SetFlag(mysql.NotNullFlag) 584 585 // mysql.TypeLongBlob + notnull 586 ftTypeLongBlobNotNull := types.NewFieldType(mysql.TypeLongBlob) 587 ftTypeLongBlobNotNull.SetFlag(mysql.NotNullFlag) 588 589 // mysql.TypeBlob + notnull 590 ftTypeBlobNotNull := types.NewFieldType(mysql.TypeBlob) 591 ftTypeBlobNotNull.SetFlag(mysql.NotNullFlag) 592 593 // mysql.TypeVarString + notnull 594 ftTypeVarStringNotNull := types.NewFieldType(mysql.TypeVarString) 595 ftTypeVarStringNotNull.SetFlag(mysql.NotNullFlag) 596 597 // mysql.TypeString + notnull 598 ftTypeStringNotNull := types.NewFieldType(mysql.TypeString) 599 ftTypeStringNotNull.SetFlag(mysql.NotNullFlag) 600 601 // mysql.TypeBit + notnull 602 ftTypeBitNotNull := types.NewFieldType(mysql.TypeBit) 603 ftTypeBitNotNull.SetFlag(mysql.NotNullFlag) 604 605 // mysql.TypeJSON + notnull 606 ftTypeJSONNotNull := types.NewFieldType(mysql.TypeJSON) 607 ftTypeJSONNotNull.SetFlag(mysql.NotNullFlag) 608 609 // mysql.TypeEnum + notnull + nodefault 610 ftTypeEnumNotNull := types.NewFieldType(mysql.TypeEnum) 611 ftTypeEnumNotNull.SetFlag(mysql.NotNullFlag) 612 ftTypeEnumNotNull.SetElems([]string{"e0", "e1"}) 613 614 // mysql.TypeEnum + null 615 ftTypeEnumNull := types.NewFieldType(mysql.TypeEnum) 616 617 // mysql.TypeSet + notnull 618 ftTypeSetNotNull := types.NewFieldType(mysql.TypeSet) 619 ftTypeSetNotNull.SetFlag(mysql.NotNullFlag) 620 ftTypeSetNotNull.SetElems([]string{"1", "e"}) 621 622 // mysql.TypeGeometry + notnull 623 ftTypeGeometryNotNull := types.NewFieldType(mysql.TypeGeometry) 624 ftTypeGeometryNotNull.SetFlag(mysql.NotNullFlag) 625 626 testCases := []struct { 627 Name string 628 ColInfo timodel.ColumnInfo 629 Res interface{} 630 }{ 631 { 632 Name: "mysql flag null", 633 ColInfo: timodel.ColumnInfo{FieldType: *ftNull}, 634 Res: nil, 635 }, 636 { 637 Name: "mysql.TypeTiny + notnull + nodefault", 638 ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNotNull.Clone()}, 639 Res: int64(0), 640 }, 641 { 642 Name: "mysql.TypeTiny + notnull + default", 643 ColInfo: timodel.ColumnInfo{ 644 OriginDefaultValue: "-128", 645 FieldType: *ftTinyIntNotNull, 646 }, 647 Res: int64(-128), 648 }, 649 { 650 Name: "mysql.TypeTiny + notnull + default + unsigned", 651 ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNotNullUnSigned}, 652 Res: uint64(0), 653 }, 654 { 655 Name: "mysql.TypeTiny + notnull + unsigned", 656 ColInfo: timodel.ColumnInfo{OriginDefaultValue: "127", FieldType: *ftTinyIntNotNullUnSigned}, 657 Res: uint64(127), 658 }, 659 { 660 Name: "mysql.TypeTiny + null + default", 661 ColInfo: timodel.ColumnInfo{ 662 OriginDefaultValue: "-128", 663 FieldType: *ftTinyIntNull, 664 }, 665 Res: int64(-128), 666 }, 667 { 668 Name: "mysql.TypeTiny + null + nodefault", 669 ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNull}, 670 Res: nil, 671 }, 672 { 673 Name: "mysql.TypeShort, others testCases same as tiny", 674 ColInfo: timodel.ColumnInfo{FieldType: *ftShortNotNull}, 675 Res: int64(0), 676 }, 677 { 678 Name: "mysql.TypeLong, others testCases same as tiny", 679 ColInfo: timodel.ColumnInfo{FieldType: *ftLongNotNull}, 680 Res: int64(0), 681 }, 682 { 683 Name: "mysql.TypeLonglong, others testCases same as tiny", 684 ColInfo: timodel.ColumnInfo{FieldType: *ftLongLongNotNull}, 685 Res: int64(0), 686 }, 687 { 688 Name: "mysql.TypeInt24, others testCases same as tiny", 689 ColInfo: timodel.ColumnInfo{FieldType: *ftInt24NotNull}, 690 Res: int64(0), 691 }, 692 { 693 Name: "mysql.TypeFloat + notnull + nodefault", 694 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeFloatNotNull}, 695 Res: float32(0), 696 }, 697 { 698 Name: "mysql.TypeFloat + notnull + default", 699 ColInfo: timodel.ColumnInfo{ 700 OriginDefaultValue: float32(-3.1415), 701 FieldType: *ftTypeFloatNotNull, 702 }, 703 Res: float32(-3.1415), 704 }, 705 { 706 Name: "mysql.TypeFloat + notnull + default + unsigned", 707 ColInfo: timodel.ColumnInfo{ 708 OriginDefaultValue: float32(3.1415), 709 FieldType: *ftTypeFloatNotNullUnSigned, 710 }, 711 Res: float32(3.1415), 712 }, 713 { 714 Name: "mysql.TypeFloat + notnull + unsigned", 715 ColInfo: timodel.ColumnInfo{ 716 FieldType: *ftTypeFloatNotNullUnSigned, 717 }, 718 Res: float32(0), 719 }, 720 { 721 Name: "mysql.TypeFloat + null + default", 722 ColInfo: timodel.ColumnInfo{ 723 OriginDefaultValue: float32(-3.1415), 724 FieldType: *ftTypeFloatNull, 725 }, 726 Res: float32(-3.1415), 727 }, 728 { 729 Name: "mysql.TypeFloat + null + nodefault", 730 ColInfo: timodel.ColumnInfo{ 731 FieldType: *ftTypeFloatNull, 732 }, 733 Res: nil, 734 }, 735 { 736 Name: "mysql.TypeDouble, other testCases same as float", 737 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDoubleNotNull}, 738 Res: float64(0), 739 }, 740 { 741 Name: "mysql.TypeNewDecimal + notnull + nodefault", 742 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDecimalNotNull}, 743 Res: "0", // related with Flen and Decimal 744 }, 745 { 746 Name: "mysql.TypeNewDecimal + null + nodefault", 747 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDecimalNull}, 748 Res: nil, 749 }, 750 { 751 Name: "mysql.TypeNull", 752 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNull}, 753 Res: nil, 754 }, 755 { 756 Name: "mysql.TypeTimestamp + notnull + nodefault", 757 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeTimestampNotNull}, 758 Res: "0000-00-00 00:00:00", 759 }, 760 { 761 Name: "mysql.TypeDate, other testCases same as TypeTimestamp", 762 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDateNotNull}, 763 Res: "0000-00-00", 764 }, 765 { 766 Name: "mysql.TypeDuration, other testCases same as TypeTimestamp", 767 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDurationNotNull}, 768 Res: "00:00:00", 769 }, 770 { 771 Name: "mysql.TypeDatetime, other testCases same as TypeTimestamp", 772 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDatetimeNotNull}, 773 Res: "0000-00-00 00:00:00", 774 }, 775 { 776 Name: "mysql.TypeYear + notnull + nodefault", 777 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeYearNotNull}, 778 Res: int64(0), 779 }, 780 { 781 Name: "mysql.TypeYear + notnull + default", 782 ColInfo: timodel.ColumnInfo{ 783 OriginDefaultValue: "2021", 784 FieldType: *ftTypeYearNotNull, 785 }, 786 Res: int64(2021), 787 }, 788 { 789 Name: "mysql.TypeNewDate", 790 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDateNotNull}, 791 Res: nil, // [TODO] seems not support by TiDB, need check 792 }, 793 { 794 Name: "mysql.TypeVarchar + notnull + nodefault", 795 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeVarcharNotNull}, 796 Res: []byte{}, 797 }, 798 { 799 Name: "mysql.TypeVarchar + notnull + default", 800 ColInfo: timodel.ColumnInfo{ 801 OriginDefaultValue: "e0", 802 FieldType: *ftTypeVarcharNotNull, 803 }, 804 Res: []byte("e0"), 805 }, 806 { 807 Name: "mysql.TypeTinyBlob", 808 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeTinyBlobNotNull}, 809 Res: []byte{}, 810 }, 811 { 812 Name: "mysql.TypeMediumBlob", 813 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeMediumBlobNotNull}, 814 Res: []byte{}, 815 }, 816 { 817 Name: "mysql.TypeLongBlob", 818 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeLongBlobNotNull}, 819 Res: []byte{}, 820 }, 821 { 822 Name: "mysql.TypeBlob", 823 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeBlobNotNull}, 824 Res: []byte{}, 825 }, 826 { 827 Name: "mysql.TypeVarString", 828 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeVarStringNotNull}, 829 Res: []byte{}, 830 }, 831 { 832 Name: "mysql.TypeString", 833 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeStringNotNull}, 834 Res: []byte{}, 835 }, 836 { 837 Name: "mysql.TypeBit", 838 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeBitNotNull}, 839 Res: uint64(0), 840 }, 841 // BLOB, TEXT, GEOMETRY or JSON column can't have a default value 842 { 843 Name: "mysql.TypeJSON", 844 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeJSONNotNull}, 845 Res: "null", 846 }, 847 { 848 Name: "mysql.TypeEnum + notnull + nodefault", 849 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeEnumNotNull}, 850 // TypeEnum value will be a string and then translate to []byte 851 // NotNull && no default will choose first element 852 Res: uint64(1), 853 }, 854 { 855 Name: "mysql.TypeEnum + null", 856 ColInfo: timodel.ColumnInfo{ 857 FieldType: *ftTypeEnumNull, 858 }, 859 Res: nil, 860 }, 861 { 862 Name: "mysql.TypeSet + notnull", 863 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeSetNotNull}, 864 Res: uint64(0), 865 }, 866 { 867 Name: "mysql.TypeGeometry", 868 ColInfo: timodel.ColumnInfo{FieldType: *ftTypeGeometryNotNull}, 869 Res: nil, // not support yet 870 }, 871 } 872 873 tz, err := util.GetTimezone(config.GetGlobalServerConfig().TZ) 874 require.NoError(t, err) 875 for _, tc := range testCases { 876 _, val, _, _, _ := getDefaultOrZeroValue(&tc.ColInfo, tz) 877 require.Equal(t, tc.Res, val, tc.Name) 878 } 879 880 colInfo := timodel.ColumnInfo{ 881 OriginDefaultValue: "-3.14", // no float 882 FieldType: *ftTypeNewDecimalNotNull, 883 } 884 _, val, _, _, _ := getDefaultOrZeroValue(&colInfo, tz) 885 decimal := new(types.MyDecimal) 886 err = decimal.FromString([]byte("-3.14")) 887 require.NoError(t, err) 888 require.Equal(t, decimal.String(), val, "mysql.TypeNewDecimal + notnull + default") 889 890 colInfo = timodel.ColumnInfo{ 891 OriginDefaultValue: "2020-11-19 12:12:12", 892 FieldType: *ftTypeTimestampNotNull, 893 } 894 _, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz) 895 expected, err := types.ParseTimeFromFloatString( 896 types.DefaultStmtNoWarningContext, 897 "2020-11-19 20:12:12", colInfo.FieldType.GetType(), colInfo.FieldType.GetDecimal()) 898 require.NoError(t, err) 899 require.Equal(t, expected.String(), val, "mysql.TypeTimestamp + notnull + default") 900 901 colInfo = timodel.ColumnInfo{ 902 OriginDefaultValue: "2020-11-19 12:12:12", 903 FieldType: *ftTypeTimestampNull, 904 } 905 _, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz) 906 expected, err = types.ParseTimeFromFloatString( 907 types.DefaultStmtNoWarningContext, 908 "2020-11-19 20:12:12", colInfo.FieldType.GetType(), colInfo.FieldType.GetDecimal()) 909 require.NoError(t, err) 910 require.Equal(t, expected.String(), val, "mysql.TypeTimestamp + null + default") 911 912 colInfo = timodel.ColumnInfo{ 913 OriginDefaultValue: "e1", 914 FieldType: *ftTypeEnumNotNull, 915 } 916 _, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz) 917 expectedEnum, err := types.ParseEnumName(colInfo.FieldType.GetElems(), "e1", colInfo.FieldType.GetCollate()) 918 require.NoError(t, err) 919 require.Equal(t, expectedEnum.Value, val, "mysql.TypeEnum + notnull + default") 920 921 colInfo = timodel.ColumnInfo{ 922 OriginDefaultValue: "1,e", 923 FieldType: *ftTypeSetNotNull, 924 } 925 _, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz) 926 expectedSet, err := types.ParseSetName(colInfo.FieldType.GetElems(), "1,e", colInfo.FieldType.GetCollate()) 927 require.NoError(t, err) 928 require.Equal(t, expectedSet.Value, val, "mysql.TypeSet + notnull + default") 929 } 930 931 func TestE2ERowLevelChecksum(t *testing.T) { 932 helper := NewSchemaTestHelper(t) 933 defer helper.Close() 934 935 tk := helper.Tk() 936 // upstream TiDB enable checksum functionality 937 tk.MustExec("set global tidb_enable_row_level_checksum = 1") 938 helper.Tk().MustExec("use test") 939 940 // changefeed enable checksum functionality 941 replicaConfig := config.GetDefaultReplicaConfig() 942 replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness 943 filter, err := filter.NewFilter(replicaConfig, "") 944 require.NoError(t, err) 945 946 ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope) 947 require.NoError(t, err) 948 949 changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row") 950 schemaStorage, err := NewSchemaStorage(helper.Storage(), 951 ver.Ver, false, changefeed, util.RoleTester, filter) 952 require.NoError(t, err) 953 require.NotNil(t, schemaStorage) 954 955 createTableSQL := `create table t ( 956 id int primary key auto_increment, 957 958 c_tinyint tinyint null, 959 c_smallint smallint null, 960 c_mediumint mediumint null, 961 c_int int null, 962 c_bigint bigint null, 963 964 c_unsigned_tinyint tinyint unsigned null, 965 c_unsigned_smallint smallint unsigned null, 966 c_unsigned_mediumint mediumint unsigned null, 967 c_unsigned_int int unsigned null, 968 c_unsigned_bigint bigint unsigned null, 969 970 c_float float null, 971 c_double double null, 972 c_decimal decimal null, 973 c_decimal_2 decimal(10, 4) null, 974 975 c_unsigned_float float unsigned null, 976 c_unsigned_double double unsigned null, 977 c_unsigned_decimal decimal unsigned null, 978 c_unsigned_decimal_2 decimal(10, 4) unsigned null, 979 980 c_date date null, 981 c_datetime datetime null, 982 c_timestamp timestamp null, 983 c_time time null, 984 c_year year null, 985 986 c_tinytext tinytext null, 987 c_text text null, 988 c_mediumtext mediumtext null, 989 c_longtext longtext null, 990 991 c_tinyblob tinyblob null, 992 c_blob blob null, 993 c_mediumblob mediumblob null, 994 c_longblob longblob null, 995 996 c_char char(16) null, 997 c_varchar varchar(16) null, 998 c_binary binary(16) null, 999 c_varbinary varbinary(16) null, 1000 1001 c_enum enum ('a','b','c') null, 1002 c_set set ('a','b','c') null, 1003 c_bit bit(64) null, 1004 c_json json null, 1005 1006 -- gbk dmls 1007 name varchar(128) CHARACTER SET gbk, 1008 country char(32) CHARACTER SET gbk, 1009 city varchar(64), 1010 description text CHARACTER SET gbk, 1011 image tinyblob 1012 );` 1013 job := helper.DDL2Job(createTableSQL) 1014 err = schemaStorage.HandleDDLJob(job) 1015 require.NoError(t, err) 1016 1017 ts := schemaStorage.GetLastSnapshot().CurrentTs() 1018 schemaStorage.AdvanceResolvedTs(ver.Ver) 1019 1020 mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, replicaConfig.Integrity).(*mounter) 1021 1022 ctx, cancel := context.WithCancel(context.Background()) 1023 defer cancel() 1024 1025 tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "t") 1026 require.True(t, ok) 1027 1028 tk.Session().GetSessionVars().EnableRowLevelChecksum = true 1029 1030 insertDataSQL := `insert into t values ( 1031 2, 1032 1, 2, 3, 4, 5, 1033 1, 2, 3, 4, 5, 1034 2020.0202, 2020.0303, 2020.0404, 2021.1208, 1035 3.1415, 2.7182, 8000, 179394.233, 1036 '2020-02-20', '2020-02-20 02:20:20', '2020-02-20 02:20:20', '02:20:20', '2020', 1037 '89504E470D0A1A0A', '89504E470D0A1A0A', '89504E470D0A1A0A', '89504E470D0A1A0A', 1038 x'89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A', 1039 '89504E470D0A1A0A', '89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A', 1040 'b', 'b,c', b'1000001', '{ 1041 "key1": "value1", 1042 "key2": "value2", 1043 "key3": "123" 1044 }', 1045 '测试', "中国", "上海", "你好,世界", 0xC4E3BAC3CAC0BDE7 1046 );` 1047 tk.MustExec(insertDataSQL) 1048 1049 key, value := getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID) 1050 rawKV := &model.RawKVEntry{ 1051 OpType: model.OpTypePut, 1052 Key: key, 1053 Value: value, 1054 StartTs: ts - 1, 1055 CRTs: ts + 1, 1056 } 1057 row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1058 require.NoError(t, err) 1059 require.NotNil(t, row) 1060 require.NotNil(t, row.Checksum) 1061 1062 expected, ok := mounter.decoder.GetChecksum() 1063 require.True(t, ok) 1064 require.Equal(t, expected, row.Checksum.Current) 1065 require.False(t, row.Checksum.Corrupted) 1066 1067 // avro encoder enable checksum functionality. 1068 codecConfig := codecCommon.NewConfig(config.ProtocolAvro) 1069 codecConfig.EnableTiDBExtension = true 1070 codecConfig.EnableRowChecksum = true 1071 codecConfig.AvroDecimalHandlingMode = "string" 1072 codecConfig.AvroBigintUnsignedHandlingMode = "string" 1073 1074 avroEncoder, err := avro.SetupEncoderAndSchemaRegistry4Testing(ctx, codecConfig) 1075 defer avro.TeardownEncoderAndSchemaRegistry4Testing() 1076 require.NoError(t, err) 1077 1078 topic := "test.t" 1079 1080 err = avroEncoder.AppendRowChangedEvent(ctx, topic, row, func() {}) 1081 require.NoError(t, err) 1082 msg := avroEncoder.Build() 1083 require.Len(t, msg, 1) 1084 1085 schemaM, err := avro.NewConfluentSchemaManager( 1086 ctx, "http://127.0.0.1:8081", nil) 1087 require.NoError(t, err) 1088 1089 // decoder enable checksum functionality. 1090 decoder := avro.NewDecoder(codecConfig, schemaM, topic) 1091 err = decoder.AddKeyValue(msg[0].Key, msg[0].Value) 1092 require.NoError(t, err) 1093 1094 messageType, hasNext, err := decoder.HasNext() 1095 require.NoError(t, err) 1096 require.True(t, hasNext) 1097 require.Equal(t, model.MessageTypeRow, messageType) 1098 1099 row, err = decoder.NextRowChangedEvent() 1100 // no error, checksum verification passed. 1101 require.NoError(t, err) 1102 } 1103 1104 func TestTimezoneDefaultValue(t *testing.T) { 1105 helper := NewSchemaTestHelper(t) 1106 defer helper.Close() 1107 1108 _ = helper.DDL2Event(`create table test.t(a int primary key)`) 1109 insertEvent := helper.DML2Event(`insert into test.t values (1)`, "test", "t") 1110 require.NotNil(t, insertEvent) 1111 1112 tableInfo, ok := helper.schemaStorage.GetLastSnapshot().TableByName("test", "t") 1113 require.True(t, ok) 1114 1115 key, oldValue := helper.getLastKeyValue(tableInfo.ID) 1116 1117 _ = helper.DDL2Event(`alter table test.t add column b timestamp default '2023-02-09 13:00:00'`) 1118 ts := helper.schemaStorage.GetLastSnapshot().CurrentTs() 1119 rawKV := &model.RawKVEntry{ 1120 OpType: model.OpTypePut, 1121 Key: key, 1122 OldValue: oldValue, 1123 StartTs: ts - 1, 1124 CRTs: ts + 1, 1125 } 1126 polymorphicEvent := model.NewPolymorphicEvent(rawKV) 1127 err := helper.mounter.DecodeEvent(context.Background(), polymorphicEvent) 1128 require.NoError(t, err) 1129 1130 event := polymorphicEvent.Row 1131 require.NotNil(t, event) 1132 require.Equal(t, "2023-02-09 13:00:00", event.PreColumns[1].Value.(string)) 1133 } 1134 1135 func TestVerifyChecksumTime(t *testing.T) { 1136 replicaConfig := config.GetDefaultReplicaConfig() 1137 replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness 1138 replicaConfig.Integrity.CorruptionHandleLevel = integrity.CorruptionHandleLevelError 1139 1140 helper := NewSchemaTestHelperWithReplicaConfig(t, replicaConfig) 1141 defer helper.Close() 1142 1143 helper.Tk().MustExec("set global tidb_enable_row_level_checksum = 1") 1144 helper.Tk().MustExec("use test") 1145 1146 helper.Tk().MustExec("set global time_zone = '-5:00'") 1147 _ = helper.DDL2Event(`CREATE table TBL2 (a int primary key, b TIMESTAMP)`) 1148 event := helper.DML2Event(`INSERT INTO TBL2 VALUES (1, '2023-02-09 13:00:00')`, "test", "TBL2") 1149 require.NotNil(t, event) 1150 1151 _ = helper.DDL2Event("create table t (a timestamp primary key, b int)") 1152 event = helper.DML2Event("insert into t values ('2023-02-09 13:00:00', 1)", "test", "t") 1153 require.NotNil(t, event) 1154 } 1155 1156 func TestDecodeRowEnableChecksum(t *testing.T) { 1157 helper := NewSchemaTestHelper(t) 1158 defer helper.Close() 1159 1160 tk := helper.Tk() 1161 1162 tk.MustExec("set global tidb_enable_row_level_checksum = 1") 1163 helper.Tk().MustExec("use test") 1164 1165 replicaConfig := config.GetDefaultReplicaConfig() 1166 replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness 1167 filter, err := filter.NewFilter(replicaConfig, "") 1168 require.NoError(t, err) 1169 1170 ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope) 1171 require.NoError(t, err) 1172 1173 changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row") 1174 schemaStorage, err := NewSchemaStorage(helper.Storage(), 1175 ver.Ver, false, changefeed, util.RoleTester, filter) 1176 require.NoError(t, err) 1177 require.NotNil(t, schemaStorage) 1178 1179 createTableDDL := "create table t (id int primary key, a int)" 1180 job := helper.DDL2Job(createTableDDL) 1181 err = schemaStorage.HandleDDLJob(job) 1182 require.NoError(t, err) 1183 1184 ts := schemaStorage.GetLastSnapshot().CurrentTs() 1185 schemaStorage.AdvanceResolvedTs(ver.Ver) 1186 1187 mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, replicaConfig.Integrity).(*mounter) 1188 1189 ctx := context.Background() 1190 1191 tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "t") 1192 require.True(t, ok) 1193 1194 // row without checksum 1195 tk.Session().GetSessionVars().EnableRowLevelChecksum = false 1196 tk.MustExec("insert into t values (1, 10)") 1197 1198 key, value := getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID) 1199 rawKV := &model.RawKVEntry{ 1200 OpType: model.OpTypePut, 1201 Key: key, 1202 Value: value, 1203 StartTs: ts - 1, 1204 CRTs: ts + 1, 1205 } 1206 1207 row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1208 require.NoError(t, err) 1209 require.NotNil(t, row) 1210 // the upstream tidb does not enable checksum, so the checksum is nil 1211 require.Nil(t, row.Checksum) 1212 1213 // row with one checksum 1214 tk.Session().GetSessionVars().EnableRowLevelChecksum = true 1215 tk.MustExec("insert into t values (2, 20)") 1216 1217 key, value = getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID) 1218 rawKV = &model.RawKVEntry{ 1219 OpType: model.OpTypePut, 1220 Key: key, 1221 Value: value, 1222 StartTs: ts - 1, 1223 CRTs: ts + 1, 1224 } 1225 row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1226 require.NoError(t, err) 1227 require.NotNil(t, row) 1228 require.NotNil(t, row.Checksum) 1229 1230 expected, ok := mounter.decoder.GetChecksum() 1231 require.True(t, ok) 1232 require.Equal(t, expected, row.Checksum.Current) 1233 require.False(t, row.Checksum.Corrupted) 1234 1235 // row with 2 checksum 1236 tk.MustExec("insert into t values (3, 30)") 1237 job = helper.DDL2Job("alter table t change column a a varchar(10)") 1238 err = schemaStorage.HandleDDLJob(job) 1239 require.NoError(t, err) 1240 1241 key, value = getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID) 1242 rawKV = &model.RawKVEntry{ 1243 OpType: model.OpTypePut, 1244 Key: key, 1245 Value: value, 1246 StartTs: ts - 1, 1247 CRTs: ts + 1, 1248 } 1249 row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1250 require.NoError(t, err) 1251 require.NotNil(t, row) 1252 require.NotNil(t, row.Checksum) 1253 1254 first, ok := mounter.decoder.GetChecksum() 1255 require.True(t, ok) 1256 1257 extra, ok := mounter.decoder.GetExtraChecksum() 1258 require.True(t, ok) 1259 1260 if row.Checksum.Current != first { 1261 require.Equal(t, extra, row.Checksum.Current) 1262 } else { 1263 require.Equal(t, first, row.Checksum.Current) 1264 } 1265 require.False(t, row.Checksum.Corrupted) 1266 1267 // hack the table info to make the checksum corrupted 1268 tableInfo.Columns[0].FieldType = *types.NewFieldType(mysql.TypeVarchar) 1269 1270 // corrupt-handle-level default to warn, so no error, but the checksum is corrupted 1271 row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1272 require.NoError(t, err) 1273 require.NotNil(t, row.Checksum) 1274 require.True(t, row.Checksum.Corrupted) 1275 1276 mounter.integrity.CorruptionHandleLevel = integrity.CorruptionHandleLevelError 1277 _, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1278 require.Error(t, err) 1279 require.ErrorIs(t, err, cerror.ErrCorruptedDataMutation) 1280 1281 job = helper.DDL2Job("drop table t") 1282 err = schemaStorage.HandleDDLJob(job) 1283 require.NoError(t, err) 1284 } 1285 1286 func TestDecodeRow(t *testing.T) { 1287 helper := NewSchemaTestHelper(t) 1288 defer helper.Close() 1289 1290 helper.Tk().MustExec("set @@tidb_enable_clustered_index=1;") 1291 helper.Tk().MustExec("use test;") 1292 1293 changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row") 1294 1295 ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope) 1296 require.NoError(t, err) 1297 1298 cfg := config.GetDefaultReplicaConfig() 1299 1300 filter, err := filter.NewFilter(cfg, "") 1301 require.NoError(t, err) 1302 1303 schemaStorage, err := NewSchemaStorage(helper.Storage(), 1304 ver.Ver, false, changefeed, util.RoleTester, filter) 1305 require.NoError(t, err) 1306 1307 // apply ddl to schemaStorage 1308 ddl := "create table test.student(id int primary key, name char(50), age int, gender char(10))" 1309 job := helper.DDL2Job(ddl) 1310 err = schemaStorage.HandleDDLJob(job) 1311 require.NoError(t, err) 1312 1313 ts := schemaStorage.GetLastSnapshot().CurrentTs() 1314 1315 schemaStorage.AdvanceResolvedTs(ver.Ver) 1316 1317 mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, cfg.Integrity).(*mounter) 1318 1319 helper.Tk().MustExec(`insert into student values(1, "dongmen", 20, "male")`) 1320 helper.Tk().MustExec(`update student set age = 27 where id = 1`) 1321 1322 ctx := context.Background() 1323 decodeAndCheckRowInTable := func(tableID int64, f func(key []byte, value []byte) *model.RawKVEntry) { 1324 walkTableSpanInStore(t, helper.Storage(), tableID, func(key []byte, value []byte) { 1325 rawKV := f(key, value) 1326 1327 row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV) 1328 require.NoError(t, err) 1329 require.NotNil(t, row) 1330 1331 if row.Columns != nil { 1332 require.NotNil(t, mounter.decoder) 1333 } 1334 1335 if row.PreColumns != nil { 1336 require.NotNil(t, mounter.preDecoder) 1337 } 1338 }) 1339 } 1340 1341 toRawKV := func(key []byte, value []byte) *model.RawKVEntry { 1342 return &model.RawKVEntry{ 1343 OpType: model.OpTypePut, 1344 Key: key, 1345 Value: value, 1346 StartTs: ts - 1, 1347 CRTs: ts + 1, 1348 } 1349 } 1350 1351 tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "student") 1352 require.True(t, ok) 1353 1354 decodeAndCheckRowInTable(tableInfo.ID, toRawKV) 1355 decodeAndCheckRowInTable(tableInfo.ID, toRawKV) 1356 1357 job = helper.DDL2Job("drop table student") 1358 err = schemaStorage.HandleDDLJob(job) 1359 require.NoError(t, err) 1360 } 1361 1362 // TestDecodeEventIgnoreRow tests a PolymorphicEvent.Row is nil 1363 // if this event should be filter out by filter. 1364 func TestDecodeEventIgnoreRow(t *testing.T) { 1365 helper := NewSchemaTestHelper(t) 1366 defer helper.Close() 1367 helper.Tk().MustExec("use test;") 1368 1369 ddls := []string{ 1370 "create table test.student(id int primary key, name char(50), age int, gender char(10))", 1371 "create table test.computer(id int primary key, brand char(50), price int)", 1372 "create table test.poet(id int primary key, name char(50), works char(100))", 1373 } 1374 1375 cfID := model.DefaultChangeFeedID("changefeed-test-ignore-event") 1376 1377 cfg := config.GetDefaultReplicaConfig() 1378 cfg.Filter.Rules = []string{"test.student", "test.computer"} 1379 f, err := filter.NewFilter(cfg, "") 1380 require.Nil(t, err) 1381 ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope) 1382 require.Nil(t, err) 1383 1384 schemaStorage, err := NewSchemaStorage(helper.Storage(), 1385 ver.Ver, false, cfID, util.RoleTester, f) 1386 require.Nil(t, err) 1387 // apply ddl to schemaStorage 1388 for _, ddl := range ddls { 1389 job := helper.DDL2Job(ddl) 1390 err = schemaStorage.HandleDDLJob(job) 1391 require.Nil(t, err) 1392 } 1393 1394 ts := schemaStorage.GetLastSnapshot().CurrentTs() 1395 schemaStorage.AdvanceResolvedTs(ver.Ver) 1396 mounter := NewMounter(schemaStorage, cfID, time.Local, f, cfg.Integrity).(*mounter) 1397 1398 type testCase struct { 1399 schema string 1400 table string 1401 columns []interface{} 1402 ignored bool 1403 } 1404 1405 testCases := []testCase{ 1406 { 1407 schema: "test", 1408 table: "student", 1409 columns: []interface{}{1, "dongmen", 20, "male"}, 1410 ignored: false, 1411 }, 1412 { 1413 schema: "test", 1414 table: "computer", 1415 columns: []interface{}{1, "apple", 19999}, 1416 ignored: false, 1417 }, 1418 // This case should be ignored by its table name. 1419 { 1420 schema: "test", 1421 table: "poet", 1422 columns: []interface{}{1, "李白", "静夜思"}, 1423 ignored: true, 1424 }, 1425 } 1426 1427 ignoredTables := make([]string, 0) 1428 tables := make([]string, 0) 1429 for _, tc := range testCases { 1430 tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName(tc.schema, tc.table) 1431 require.True(t, ok) 1432 // TODO: add other dml event type 1433 insertSQL := prepareInsertSQL(t, tableInfo, len(tc.columns)) 1434 if tc.ignored { 1435 ignoredTables = append(ignoredTables, tc.table) 1436 } else { 1437 tables = append(tables, tc.table) 1438 } 1439 helper.tk.MustExec(insertSQL, tc.columns...) 1440 } 1441 ctx := context.Background() 1442 1443 decodeAndCheckRowInTable := func(tableID int64, f func(key []byte, value []byte) *model.RawKVEntry) int { 1444 var rows int 1445 walkTableSpanInStore(t, helper.Storage(), tableID, func(key []byte, value []byte) { 1446 rawKV := f(key, value) 1447 pEvent := model.NewPolymorphicEvent(rawKV) 1448 err := mounter.DecodeEvent(ctx, pEvent) 1449 require.Nil(t, err) 1450 if pEvent.Row == nil { 1451 return 1452 } 1453 row := pEvent.Row 1454 rows++ 1455 require.Equal(t, row.TableInfo.GetSchemaName(), "test") 1456 // Now we only allow filter dml event by table, so we only check row's table. 1457 require.NotContains(t, ignoredTables, row.TableInfo.GetTableName()) 1458 require.Contains(t, tables, row.TableInfo.GetTableName()) 1459 }) 1460 return rows 1461 } 1462 1463 toRawKV := func(key []byte, value []byte) *model.RawKVEntry { 1464 return &model.RawKVEntry{ 1465 OpType: model.OpTypePut, 1466 Key: key, 1467 Value: value, 1468 StartTs: ts - 1, 1469 CRTs: ts + 1, 1470 } 1471 } 1472 1473 for _, tc := range testCases { 1474 tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName(tc.schema, tc.table) 1475 require.True(t, ok) 1476 decodeAndCheckRowInTable(tableInfo.ID, toRawKV) 1477 } 1478 } 1479 1480 func TestBuildTableInfo(t *testing.T) { 1481 cases := []struct { 1482 origin string 1483 recovered string 1484 recoveredWithNilCol string 1485 }{ 1486 { 1487 "CREATE TABLE t1 (c INT PRIMARY KEY)", 1488 "CREATE TABLE `t1` (\n" + 1489 " `c` int(0) NOT NULL,\n" + 1490 " PRIMARY KEY (`c`(0)) /*T![clustered_index] CLUSTERED */\n" + 1491 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1492 "CREATE TABLE `t1` (\n" + 1493 " `c` int(0) NOT NULL,\n" + 1494 " PRIMARY KEY (`c`(0)) /*T![clustered_index] CLUSTERED */\n" + 1495 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1496 }, 1497 { 1498 "CREATE TABLE t1 (" + 1499 " c INT UNSIGNED," + 1500 " c2 VARCHAR(10) NOT NULL," + 1501 " c3 BIT(10) NOT NULL," + 1502 " UNIQUE KEY (c2, c3)" + 1503 ")", 1504 // CDC discards field length. 1505 "CREATE TABLE `t1` (\n" + 1506 " `c` int(0) unsigned DEFAULT NULL,\n" + 1507 " `c2` varchar(0) NOT NULL,\n" + 1508 " `c3` bit(0) NOT NULL,\n" + 1509 " UNIQUE KEY `idx_0` (`c2`(0),`c3`(0))\n" + 1510 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1511 "CREATE TABLE `t1` (\n" + 1512 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1513 " `c2` varchar(0) NOT NULL,\n" + 1514 " `c3` bit(0) NOT NULL,\n" + 1515 " UNIQUE KEY `idx_0` (`c2`(0),`c3`(0))\n" + 1516 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1517 }, 1518 { 1519 "CREATE TABLE t1 (" + 1520 " c INT UNSIGNED," + 1521 " gen INT AS (c+1) VIRTUAL," + 1522 " c2 VARCHAR(10) NOT NULL," + 1523 " gen2 INT AS (c+2) STORED," + 1524 " c3 BIT(10) NOT NULL," + 1525 " PRIMARY KEY (c, c2)" + 1526 ")", 1527 // CDC discards virtual generated column, and generating expression of stored generated column. 1528 "CREATE TABLE `t1` (\n" + 1529 " `c` int(0) unsigned NOT NULL,\n" + 1530 " `c2` varchar(0) NOT NULL,\n" + 1531 " `gen2` int(0) GENERATED ALWAYS AS (pass_generated_check) STORED,\n" + 1532 " `c3` bit(0) NOT NULL,\n" + 1533 " PRIMARY KEY (`c`(0),`c2`(0)) /*T![clustered_index] CLUSTERED */\n" + 1534 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1535 "CREATE TABLE `t1` (\n" + 1536 " `c` int(0) unsigned NOT NULL,\n" + 1537 " `c2` varchar(0) NOT NULL,\n" + 1538 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1539 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1540 " PRIMARY KEY (`c`(0),`c2`(0)) /*T![clustered_index] CLUSTERED */\n" + 1541 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1542 }, 1543 { 1544 "CREATE TABLE `t1` (" + 1545 " `a` int(11) NOT NULL," + 1546 " `b` int(11) DEFAULT NULL," + 1547 " `c` int(11) DEFAULT NULL," + 1548 " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */," + 1549 " UNIQUE KEY `b` (`b`)" + 1550 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1551 "CREATE TABLE `t1` (\n" + 1552 " `a` int(0) NOT NULL,\n" + 1553 " `b` int(0) DEFAULT NULL,\n" + 1554 " `c` int(0) DEFAULT NULL,\n" + 1555 " PRIMARY KEY (`a`(0)) /*T![clustered_index] CLUSTERED */,\n" + 1556 " UNIQUE KEY `idx_1` (`b`(0))\n" + 1557 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1558 "CREATE TABLE `t1` (\n" + 1559 " `a` int(0) NOT NULL,\n" + 1560 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1561 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1562 " PRIMARY KEY (`a`(0)) /*T![clustered_index] CLUSTERED */\n" + 1563 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1564 }, 1565 { // This case is to check the primary key is correctly identified by BuildTiDBTableInfo 1566 "CREATE TABLE your_table (" + 1567 " id INT NOT NULL," + 1568 " name VARCHAR(50) NOT NULL," + 1569 " email VARCHAR(100) NOT NULL," + 1570 " age INT NOT NULL ," + 1571 " address VARCHAR(200) NOT NULL," + 1572 " PRIMARY KEY (id, name)," + 1573 " UNIQUE INDEX idx_unique_1 (id, email, age)," + 1574 " UNIQUE INDEX idx_unique_2 (name, email, address)" + 1575 " );", 1576 "CREATE TABLE `your_table` (\n" + 1577 " `id` int(0) NOT NULL,\n" + 1578 " `name` varchar(0) NOT NULL,\n" + 1579 " `email` varchar(0) NOT NULL,\n" + 1580 " `age` int(0) NOT NULL,\n" + 1581 " `address` varchar(0) NOT NULL,\n" + 1582 " PRIMARY KEY (`id`(0),`name`(0)) /*T![clustered_index] CLUSTERED */,\n" + 1583 " UNIQUE KEY `idx_1` (`id`(0),`email`(0),`age`(0)),\n" + 1584 " UNIQUE KEY `idx_2` (`name`(0),`email`(0),`address`(0))\n" + 1585 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1586 "CREATE TABLE `your_table` (\n" + 1587 " `id` int(0) NOT NULL,\n" + 1588 " `name` varchar(0) NOT NULL,\n" + 1589 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1590 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1591 " `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" + 1592 " PRIMARY KEY (`id`(0),`name`(0)) /*T![clustered_index] CLUSTERED */,\n" + 1593 " UNIQUE KEY `idx_1` (`id`(0),`omitted`(0),`omitted`(0)),\n" + 1594 " UNIQUE KEY `idx_2` (`name`(0),`omitted`(0),`omitted`(0))\n" + 1595 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1596 }, 1597 } 1598 tz, err := util.GetTimezone(config.GetGlobalServerConfig().TZ) 1599 require.NoError(t, err) 1600 p := parser.New() 1601 for i, c := range cases { 1602 stmt, err := p.ParseOneStmt(c.origin, "", "") 1603 require.NoError(t, err) 1604 originTI, err := ddl.BuildTableInfoFromAST(stmt.(*ast.CreateTableStmt)) 1605 require.NoError(t, err) 1606 cdcTableInfo := model.WrapTableInfo(0, "test", 0, originTI) 1607 colDatas, _, _, err := datum2Column(cdcTableInfo, map[int64]types.Datum{}, tz) 1608 require.NoError(t, err) 1609 e := model.RowChangedEvent{ 1610 TableInfo: cdcTableInfo, 1611 Columns: colDatas, 1612 } 1613 cols := e.GetColumns() 1614 recoveredTI := model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset) 1615 handle := sqlmodel.GetWhereHandle(recoveredTI, recoveredTI) 1616 require.NotNil(t, handle.UniqueNotNullIdx) 1617 require.Equal(t, c.recovered, showCreateTable(t, recoveredTI)) 1618 // make sure BuildTiDBTableInfo indentify the correct primary key 1619 if i == 5 { 1620 inexes := recoveredTI.Indices 1621 primaryCount := 0 1622 for i := range inexes { 1623 if inexes[i].Primary { 1624 primaryCount++ 1625 } 1626 } 1627 require.Equal(t, 1, primaryCount) 1628 require.Equal(t, 2, len(handle.UniqueNotNullIdx.Columns)) 1629 } 1630 // mimic the columns are set to nil when old value feature is disabled 1631 for i := range cols { 1632 if !cols[i].Flag.IsHandleKey() { 1633 cols[i] = nil 1634 } 1635 } 1636 recoveredTI = model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset) 1637 handle = sqlmodel.GetWhereHandle(recoveredTI, recoveredTI) 1638 require.NotNil(t, handle.UniqueNotNullIdx) 1639 require.Equal(t, c.recoveredWithNilCol, showCreateTable(t, recoveredTI)) 1640 } 1641 } 1642 1643 var tiCtx = mock.NewContext() 1644 1645 func showCreateTable(t *testing.T, ti *timodel.TableInfo) string { 1646 result := bytes.NewBuffer(make([]byte, 0, 512)) 1647 err := executor.ConstructResultOfShowCreateTable(tiCtx, ti, autoid.Allocators{}, result) 1648 require.NoError(t, err) 1649 return result.String() 1650 } 1651 1652 func TestNewDMRowChange(t *testing.T) { 1653 cases := []struct { 1654 origin string 1655 recovered string 1656 }{ 1657 { 1658 "CREATE TABLE t1 (id INT," + 1659 " a1 INT NOT NULL," + 1660 " a3 INT NOT NULL," + 1661 " UNIQUE KEY dex1(a1, a3));", 1662 "CREATE TABLE `t1` (\n" + 1663 " `id` int(0) DEFAULT NULL,\n" + 1664 " `a1` int(0) NOT NULL,\n" + 1665 " `a3` int(0) NOT NULL,\n" + 1666 " UNIQUE KEY `idx_0` (`a1`(0),`a3`(0))\n" + 1667 ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", 1668 }, 1669 } 1670 p := parser.New() 1671 for _, c := range cases { 1672 stmt, err := p.ParseOneStmt(c.origin, "", "") 1673 require.NoError(t, err) 1674 originTI, err := ddl.BuildTableInfoFromAST(stmt.(*ast.CreateTableStmt)) 1675 require.NoError(t, err) 1676 cdcTableInfo := model.WrapTableInfo(0, "test", 0, originTI) 1677 cols := []*model.Column{ 1678 { 1679 Name: "id", Type: 3, Charset: "binary", Flag: 65, Value: 1, Default: nil, 1680 }, 1681 { 1682 Name: "a1", Type: 3, Charset: "binary", Flag: 51, Value: 1, Default: nil, 1683 }, 1684 { 1685 Name: "a3", Type: 3, Charset: "binary", Flag: 51, Value: 2, Default: nil, 1686 }, 1687 } 1688 recoveredTI := model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset) 1689 require.Equal(t, c.recovered, showCreateTable(t, recoveredTI)) 1690 tableName := &model.TableName{Schema: "db", Table: "t1"} 1691 rowChange := sqlmodel.NewRowChange(tableName, nil, []interface{}{1, 1, 2}, nil, recoveredTI, nil, nil) 1692 sqlGot, argsGot := rowChange.GenSQL(sqlmodel.DMLDelete) 1693 require.Equal(t, "DELETE FROM `db`.`t1` WHERE `a1` = ? AND `a3` = ? LIMIT 1", sqlGot) 1694 require.Equal(t, []interface{}{1, 2}, argsGot) 1695 1696 sqlGot, argsGot = sqlmodel.GenDeleteSQL(rowChange, rowChange) 1697 require.Equal(t, "DELETE FROM `db`.`t1` WHERE (`a1` = ? AND `a3` = ?) OR (`a1` = ? AND `a3` = ?)", sqlGot) 1698 require.Equal(t, []interface{}{1, 2, 1, 2}, argsGot) 1699 } 1700 } 1701 1702 func TestFormatColVal(t *testing.T) { 1703 t.Parallel() 1704 1705 ftTypeFloatNotNull := types.NewFieldType(mysql.TypeFloat) 1706 ftTypeFloatNotNull.SetFlag(mysql.NotNullFlag) 1707 col := &timodel.ColumnInfo{FieldType: *ftTypeFloatNotNull} 1708 1709 var datum types.Datum 1710 1711 datum.SetFloat32(123.99) 1712 value, _, _, err := formatColVal(datum, col) 1713 require.NoError(t, err) 1714 require.EqualValues(t, float32(123.99), value) 1715 1716 datum.SetFloat32(float32(math.NaN())) 1717 value, _, warn, err := formatColVal(datum, col) 1718 require.NoError(t, err) 1719 require.Equal(t, float32(0), value) 1720 require.NotZero(t, warn) 1721 1722 datum.SetFloat32(float32(math.Inf(1))) 1723 value, _, warn, err = formatColVal(datum, col) 1724 require.NoError(t, err) 1725 require.Equal(t, float32(0), value) 1726 require.NotZero(t, warn) 1727 1728 datum.SetFloat32(float32(math.Inf(-1))) 1729 value, _, warn, err = formatColVal(datum, col) 1730 require.NoError(t, err) 1731 require.Equal(t, float32(0), value) 1732 require.NotZero(t, warn) 1733 }