github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/owner/ddl_sink_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 owner 15 16 import ( 17 "context" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/pingcap/errors" 24 "github.com/pingcap/tiflow/cdc/model" 25 "github.com/pingcap/tiflow/cdc/sink/ddlsink" 26 "github.com/pingcap/tiflow/pkg/config" 27 cerror "github.com/pingcap/tiflow/pkg/errors" 28 "github.com/pingcap/tiflow/pkg/retry" 29 "github.com/stretchr/testify/require" 30 ) 31 32 type mockSink struct { 33 ddlsink.Sink 34 checkpointTs model.Ts 35 ddl *model.DDLEvent 36 ddlMu sync.Mutex 37 ddlError error 38 } 39 40 func (m *mockSink) WriteDDLEvent(ctx context.Context, ddl *model.DDLEvent) error { 41 m.ddlMu.Lock() 42 defer m.ddlMu.Unlock() 43 time.Sleep(1 * time.Second) 44 m.ddl = ddl 45 return m.ddlError 46 } 47 48 func (m *mockSink) WriteCheckpointTs(ctx context.Context, 49 ts uint64, tables []*model.TableInfo, 50 ) error { 51 atomic.StoreUint64(&m.checkpointTs, ts) 52 return nil 53 } 54 55 func (m *mockSink) Close() {} 56 57 func (m *mockSink) GetDDL() *model.DDLEvent { 58 m.ddlMu.Lock() 59 defer m.ddlMu.Unlock() 60 return m.ddl 61 } 62 63 func newDDLSink4Test(reportErr func(err error), reportWarn func(err error)) (DDLSink, *mockSink) { 64 mockSink := &mockSink{} 65 ddlSink := newDDLSink( 66 model.DefaultChangeFeedID("changefeed-test"), 67 &model.ChangeFeedInfo{ 68 Config: config.GetDefaultReplicaConfig(), 69 }, 70 reportErr, 71 reportWarn) 72 ddlSink.(*ddlSinkImpl).sinkInitHandler = func(ctx context.Context, s *ddlSinkImpl) error { 73 s.sink = mockSink 74 return nil 75 } 76 return ddlSink, mockSink 77 } 78 79 func TestCheckpoint(t *testing.T) { 80 ddlSink, mSink := newDDLSink4Test(func(err error) {}, func(err error) {}) 81 82 ctx, cancel := context.WithCancel(context.Background()) 83 defer func() { 84 cancel() 85 ddlSink.close(ctx) 86 }() 87 ddlSink.run(ctx) 88 89 waitCheckpointGrowingUp := func(m *mockSink, targetTs model.Ts) error { 90 return retry.Do(ctx, func() error { 91 if targetTs != atomic.LoadUint64(&m.checkpointTs) { 92 return errors.New("targetTs!=checkpointTs") 93 } 94 return nil 95 }, retry.WithBackoffBaseDelay(100), retry.WithMaxTries(30)) 96 } 97 ddlSink.emitCheckpointTs(1, nil) 98 require.Nil(t, waitCheckpointGrowingUp(mSink, 1)) 99 ddlSink.emitCheckpointTs(10, nil) 100 require.Nil(t, waitCheckpointGrowingUp(mSink, 10)) 101 } 102 103 func TestExecDDLEvents(t *testing.T) { 104 ddlSink, mSink := newDDLSink4Test(func(err error) {}, func(err error) {}) 105 106 ctx, cancel := context.WithCancel(context.Background()) 107 defer func() { 108 cancel() 109 ddlSink.close(ctx) 110 }() 111 ddlSink.run(ctx) 112 113 ddlEvents := []*model.DDLEvent{ 114 {CommitTs: 1, Query: "create table t1(id int)"}, 115 {CommitTs: 2, Query: "create table t2(id int)"}, 116 {CommitTs: 3, Query: "create table t3(id int)"}, 117 } 118 119 for _, event := range ddlEvents { 120 for { 121 done, err := ddlSink.emitDDLEvent(ctx, event) 122 require.Nil(t, err) 123 if done { 124 require.Equal(t, mSink.GetDDL(), event) 125 break 126 } 127 } 128 } 129 } 130 131 func TestExecDDLError(t *testing.T) { 132 var ( 133 resultErr error 134 resultErrMu sync.Mutex 135 ) 136 readResultErr := func() error { 137 resultErrMu.Lock() 138 defer resultErrMu.Unlock() 139 return resultErr 140 } 141 142 reportFunc := func(err error) { 143 resultErrMu.Lock() 144 defer resultErrMu.Unlock() 145 resultErr = err 146 } 147 148 ddlSink, mSink := newDDLSink4Test(reportFunc, reportFunc) 149 150 ctx, cancel := context.WithCancel(context.Background()) 151 defer func() { 152 cancel() 153 ddlSink.close(ctx) 154 }() 155 156 ddlSink.run(ctx) 157 158 mSink.ddlError = cerror.ErrExecDDLFailed.GenWithStackByArgs() 159 ddl2 := &model.DDLEvent{CommitTs: 2, Query: "create table t2(id int)"} 160 for { 161 done, err := ddlSink.emitDDLEvent(ctx, ddl2) 162 require.Nil(t, err) 163 164 if done || readResultErr() != nil { 165 require.Equal(t, mSink.GetDDL(), ddl2) 166 break 167 } 168 } 169 require.True(t, cerror.ErrExecDDLFailed.Equal(readResultErr())) 170 } 171 172 func TestAddSpecialComment(t *testing.T) { 173 testCase := []struct { 174 event *model.DDLEvent 175 result string 176 }{ 177 { 178 event: &model.DDLEvent{ 179 Query: "create table t1 (id int ) shard_row_id_bits=2;", 180 }, 181 result: "CREATE TABLE `t1` (`id` INT) /*T! SHARD_ROW_ID_BITS = 2 */", 182 }, 183 { 184 event: &model.DDLEvent{ 185 Query: "create table t1 (id int ) shard_row_id_bits=2 pre_split_regions=2;", 186 }, 187 result: "CREATE TABLE `t1` (`id` INT) " + 188 "/*T! SHARD_ROW_ID_BITS = 2 */ /*T! PRE_SPLIT_REGIONS = 2 */", 189 }, 190 { 191 event: &model.DDLEvent{ 192 Query: "create table t1 (id int ) shard_row_id_bits=2 pre_split_regions=2;", 193 }, 194 result: "CREATE TABLE `t1` (`id` INT) " + 195 "/*T! SHARD_ROW_ID_BITS = 2 */ /*T! PRE_SPLIT_REGIONS = 2 */", 196 }, 197 { 198 event: &model.DDLEvent{ 199 Query: "create table t1 (id int ) shard_row_id_bits=2 " + 200 "engine=innodb pre_split_regions=2;", 201 }, 202 result: "CREATE TABLE `t1` (`id` INT) /*T! SHARD_ROW_ID_BITS = 2 */" + 203 " ENGINE = innodb /*T! PRE_SPLIT_REGIONS = 2 */", 204 }, 205 { 206 event: &model.DDLEvent{ 207 Query: "create table t1 (id int ) pre_split_regions=2 shard_row_id_bits=2;", 208 }, 209 result: "CREATE TABLE `t1` (`id` INT) /*T! PRE_SPLIT_REGIONS = 2 */" + 210 " /*T! SHARD_ROW_ID_BITS = 2 */", 211 }, 212 { 213 event: &model.DDLEvent{ 214 Query: "create table t6 (id int ) " + 215 "shard_row_id_bits=2 shard_row_id_bits=3 pre_split_regions=2;", 216 }, 217 result: "CREATE TABLE `t6` (`id` INT) /*T! SHARD_ROW_ID_BITS = 2 */ " + 218 "/*T! SHARD_ROW_ID_BITS = 3 */ /*T! PRE_SPLIT_REGIONS = 2 */", 219 }, 220 { 221 event: &model.DDLEvent{ 222 Query: "create table t1 (id int primary key auto_random(2));", 223 }, 224 result: "CREATE TABLE `t1` (`id` INT PRIMARY KEY /*T![auto_rand] AUTO_RANDOM(2) */)", 225 }, 226 { 227 event: &model.DDLEvent{ 228 Query: "create table t1 (id int primary key auto_random);", 229 }, 230 result: "CREATE TABLE `t1` (`id` INT PRIMARY KEY /*T![auto_rand] AUTO_RANDOM */)", 231 }, 232 { 233 event: &model.DDLEvent{ 234 Query: "create table t1 (id int auto_random ( 4 ) primary key);", 235 }, 236 result: "CREATE TABLE `t1` (`id` INT /*T![auto_rand] AUTO_RANDOM(4) */ PRIMARY KEY)", 237 }, 238 { 239 event: &model.DDLEvent{ 240 Query: "create table t1 (id int auto_random ( 4 ) primary key);", 241 }, 242 result: "CREATE TABLE `t1` (`id` INT /*T![auto_rand] AUTO_RANDOM(4) */ PRIMARY KEY)", 243 }, 244 { 245 event: &model.DDLEvent{ 246 Query: "create table t1 (id int auto_random ( 3 ) primary key) " + 247 "auto_random_base = 100;", 248 }, 249 result: "CREATE TABLE `t1` (`id` INT /*T![auto_rand] AUTO_RANDOM(3) */" + 250 " PRIMARY KEY) /*T![auto_rand_base] AUTO_RANDOM_BASE = 100 */", 251 }, 252 { 253 event: &model.DDLEvent{ 254 Query: "create table t1 (id int auto_random primary key) auto_random_base = 50;", 255 }, 256 result: "CREATE TABLE `t1` (`id` INT /*T![auto_rand] AUTO_RANDOM */ PRIMARY KEY)" + 257 " /*T![auto_rand_base] AUTO_RANDOM_BASE = 50 */", 258 }, 259 { 260 event: &model.DDLEvent{ 261 Query: "create table t1 (id int auto_increment key) auto_id_cache 100;", 262 }, 263 result: "CREATE TABLE `t1` (`id` INT AUTO_INCREMENT PRIMARY KEY) " + 264 "/*T![auto_id_cache] AUTO_ID_CACHE = 100 */", 265 }, 266 { 267 event: &model.DDLEvent{ 268 Query: "create table t1 (id int auto_increment unique) auto_id_cache 10;", 269 }, 270 result: "CREATE TABLE `t1` (`id` INT AUTO_INCREMENT UNIQUE KEY) " + 271 "/*T![auto_id_cache] AUTO_ID_CACHE = 10 */", 272 }, 273 { 274 event: &model.DDLEvent{ 275 Query: "create table t1 (id int) auto_id_cache = 5;", 276 }, 277 result: "CREATE TABLE `t1` (`id` INT) /*T![auto_id_cache] AUTO_ID_CACHE = 5 */", 278 }, 279 { 280 event: &model.DDLEvent{ 281 Query: "create table t1 (id int) auto_id_cache=5;", 282 }, 283 result: "CREATE TABLE `t1` (`id` INT) /*T![auto_id_cache] AUTO_ID_CACHE = 5 */", 284 }, 285 { 286 event: &model.DDLEvent{ 287 Query: "create table t1 (id int) /*T![auto_id_cache] auto_id_cache=5 */ ;", 288 }, 289 result: "CREATE TABLE `t1` (`id` INT) /*T![auto_id_cache] AUTO_ID_CACHE = 5 */", 290 }, 291 { 292 event: &model.DDLEvent{ 293 Query: "create table t1 (id int, a varchar(255), primary key (a, b) clustered);", 294 }, 295 result: "CREATE TABLE `t1` (`id` INT,`a` VARCHAR(255),PRIMARY KEY(`a`, `b`)" + 296 " /*T![clustered_index] CLUSTERED */)", 297 }, 298 { 299 event: &model.DDLEvent{ 300 Query: "create table t1(id int, v int, primary key(a) clustered);", 301 }, 302 result: "CREATE TABLE `t1` (`id` INT,`v` INT,PRIMARY KEY(`a`) " + 303 "/*T![clustered_index] CLUSTERED */)", 304 }, 305 { 306 event: &model.DDLEvent{ 307 Query: "create table t1(id int primary key clustered, v int);", 308 }, 309 result: "CREATE TABLE `t1` (`id` INT PRIMARY KEY " + 310 "/*T![clustered_index] CLUSTERED */,`v` INT)", 311 }, 312 { 313 event: &model.DDLEvent{ 314 Query: "alter table t add primary key(a) clustered;", 315 }, 316 result: "ALTER TABLE `t` ADD PRIMARY KEY(`a`) /*T![clustered_index] CLUSTERED */", 317 }, 318 { 319 event: &model.DDLEvent{ 320 Query: "create table t1 (id int, a varchar(255), primary key (a, b) nonclustered);", 321 }, 322 result: "CREATE TABLE `t1` (`id` INT,`a` VARCHAR(255),PRIMARY KEY(`a`, `b`)" + 323 " /*T![clustered_index] NONCLUSTERED */)", 324 }, 325 { 326 event: &model.DDLEvent{ 327 Query: "create table t1 (id int, a varchar(255), primary key (a, b) " + 328 "/*T![clustered_index] nonclustered */);", 329 }, 330 result: "CREATE TABLE `t1` (`id` INT,`a` VARCHAR(255),PRIMARY KEY(`a`, `b`)" + 331 " /*T![clustered_index] NONCLUSTERED */)", 332 }, 333 { 334 event: &model.DDLEvent{ 335 Query: "create table clustered_test(id int)", 336 }, 337 result: "CREATE TABLE `clustered_test` (`id` INT)", 338 }, 339 { 340 event: &model.DDLEvent{ 341 Query: "create database clustered_test", 342 }, 343 result: "CREATE DATABASE `clustered_test`", 344 }, 345 { 346 event: &model.DDLEvent{ 347 Query: "create database clustered", 348 }, 349 result: "CREATE DATABASE `clustered`", 350 }, 351 { 352 event: &model.DDLEvent{ 353 Query: "create table clustered (id int)", 354 }, 355 result: "CREATE TABLE `clustered` (`id` INT)", 356 }, 357 { 358 event: &model.DDLEvent{ 359 Query: "create table t1 (id int, a varchar(255) key clustered);", 360 }, 361 result: "CREATE TABLE `t1` (" + 362 "`id` INT,`a` VARCHAR(255) PRIMARY KEY /*T![clustered_index] CLUSTERED */)", 363 }, 364 { 365 event: &model.DDLEvent{ 366 Query: "alter table t force auto_increment = 12;", 367 }, 368 result: "ALTER TABLE `t` /*T![force_inc] FORCE */ AUTO_INCREMENT = 12", 369 }, 370 { 371 event: &model.DDLEvent{ 372 Query: "alter table t force, auto_increment = 12;", 373 }, 374 result: "ALTER TABLE `t` FORCE /* AlterTableForce is not supported */ , " + 375 "AUTO_INCREMENT = 12", 376 }, 377 { 378 event: &model.DDLEvent{ 379 Query: "create table cdc_test (id varchar(10) primary key ,c1 varchar(10)) " + 380 "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin" + 381 "/*!90000 SHARD_ROW_ID_BITS=4 PRE_SPLIT_REGIONS=3 */", 382 }, 383 result: "CREATE TABLE `cdc_test` (`id` VARCHAR(10) PRIMARY KEY,`c1` VARCHAR(10)) " + 384 "ENGINE = InnoDB DEFAULT CHARACTER SET = UTF8MB4 DEFAULT COLLATE = UTF8MB4_BIN " + 385 "/*T! SHARD_ROW_ID_BITS = 4 */ /*T! PRE_SPLIT_REGIONS = 3 */", 386 }, 387 { 388 event: &model.DDLEvent{ 389 Query: "CREATE TABLE t1 (id BIGINT NOT NULL PRIMARY KEY auto_increment, " + 390 "b varchar(255)) PLACEMENT POLICY=placement1;", 391 }, 392 result: "CREATE TABLE `t1` (`id` BIGINT NOT NULL PRIMARY KEY " + 393 "AUTO_INCREMENT,`b` VARCHAR(255)) ", 394 }, 395 { 396 event: &model.DDLEvent{ 397 Query: "CREATE TABLE `t1` (\n `a` int(11) DEFAULT NULL\n) " + 398 "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin " + 399 "/*T![placement] PLACEMENT POLICY=`p2` */", 400 }, 401 result: "CREATE TABLE `t1` (`a` INT(11) DEFAULT NULL) " + 402 "ENGINE = InnoDB DEFAULT CHARACTER SET = UTF8MB4 DEFAULT COLLATE = UTF8MB4_BIN ", 403 }, 404 { 405 event: &model.DDLEvent{ 406 Query: "CREATE TABLE t4 (" + 407 "firstname VARCHAR(25) NOT NULL," + 408 "lastname VARCHAR(25) NOT NULL," + 409 "username VARCHAR(16) NOT NULL," + 410 "email VARCHAR(35)," + 411 "joined DATE NOT NULL) " + 412 "PARTITION BY RANGE( YEAR(joined) )" + 413 " (PARTITION p0 VALUES LESS THAN (1960) PLACEMENT POLICY=p1," + 414 "PARTITION p1 VALUES LESS THAN (1970),PARTITION p2 VALUES LESS THAN (1980)," + 415 "PARTITION p3 VALUES LESS THAN (1990),PARTITION p4 VALUES LESS THAN MAXVALUE);", 416 }, 417 result: "CREATE TABLE `t4` (" + 418 "`firstname` VARCHAR(25) NOT NULL," + 419 "`lastname` VARCHAR(25) NOT NULL," + 420 "`username` VARCHAR(16) NOT NULL," + 421 "`email` VARCHAR(35)," + 422 "`joined` DATE NOT NULL) " + 423 "PARTITION BY RANGE (YEAR(`joined`)) " + 424 "(PARTITION `p0` VALUES LESS THAN (1960) ,PARTITION `p1` VALUES LESS THAN (1970)," + 425 "PARTITION `p2` VALUES LESS THAN (1980),PARTITION `p3` VALUES LESS THAN (1990)," + 426 "PARTITION `p4` VALUES LESS THAN (MAXVALUE))", 427 }, 428 { 429 event: &model.DDLEvent{ 430 Query: "ALTER TABLE t3 PLACEMENT POLICY=DEFAULT;", 431 }, 432 result: "ALTER TABLE `t3`", 433 }, 434 { 435 event: &model.DDLEvent{ 436 Query: "ALTER TABLE t1 PLACEMENT POLICY=p10", 437 }, 438 result: "ALTER TABLE `t1`", 439 }, 440 { 441 event: &model.DDLEvent{ 442 Query: "ALTER TABLE t1 PLACEMENT POLICY=p10, add d text(50)", 443 }, 444 result: "ALTER TABLE `t1` ADD COLUMN `d` TEXT(50)", 445 }, 446 { 447 event: &model.DDLEvent{ 448 Query: "alter table tp PARTITION p1 placement policy p2", 449 }, 450 result: "", 451 }, 452 { 453 event: &model.DDLEvent{ 454 Query: "alter table t add d text(50) PARTITION p1 placement policy p2", 455 }, 456 result: "ALTER TABLE `t` ADD COLUMN `d` TEXT(50)", 457 }, 458 { 459 event: &model.DDLEvent{ 460 Query: "alter table tp set tiflash replica 1 PARTITION p1 placement policy p2", 461 }, 462 result: "ALTER TABLE `tp` SET TIFLASH REPLICA 1", 463 }, 464 { 465 event: &model.DDLEvent{ 466 Query: "ALTER DATABASE TestResetPlacementDB PLACEMENT POLICY SET DEFAULT", 467 }, 468 result: "", 469 }, 470 471 { 472 event: &model.DDLEvent{ 473 Query: "ALTER DATABASE TestResetPlacementDB PLACEMENT POLICY p1 charset utf8mb4", 474 }, 475 result: "ALTER DATABASE `TestResetPlacementDB` CHARACTER SET = utf8mb4", 476 }, 477 { 478 event: &model.DDLEvent{ 479 Query: "/*T![placement] ALTER DATABASE `db1` PLACEMENT POLICY = `p1` */", 480 }, 481 result: "", 482 }, 483 { 484 event: &model.DDLEvent{ 485 Query: "ALTER PLACEMENT POLICY p3 PRIMARY_REGION='us-east-1' " + 486 "REGIONS='us-east-1,us-east-2,us-west-1';", 487 }, 488 result: "", 489 }, 490 { 491 event: &model.DDLEvent{ 492 Query: "CREATE TABLE t1(t datetime) TTL=`t` + INTERVAL 1 DAY", 493 }, 494 result: "CREATE TABLE `t1` (`t` DATETIME) " + 495 "/*T![ttl] TTL = `t` + INTERVAL 1 DAY */ /*T![ttl] TTL_ENABLE = 'OFF' */", 496 }, 497 { 498 event: &model.DDLEvent{ 499 Query: "CREATE TABLE t1(t datetime) TTL=`t` + INTERVAL 1 DAY TTL_ENABLE='ON'", 500 }, 501 result: "CREATE TABLE `t1` (`t` DATETIME) " + 502 "/*T![ttl] TTL = `t` + INTERVAL 1 DAY */ /*T![ttl] TTL_ENABLE = 'OFF' */", 503 }, 504 { 505 event: &model.DDLEvent{ 506 Query: "ALTER TABLE t1 TTL=`t` + INTERVAL 1 DAY", 507 }, 508 result: "ALTER TABLE `t1` " + 509 "/*T![ttl] TTL = `t` + INTERVAL 1 DAY */ /*T![ttl] TTL_ENABLE = 'OFF' */", 510 }, 511 { 512 event: &model.DDLEvent{ 513 Query: "ALTER TABLE t1 TTL=`t` + INTERVAL 1 DAY TTL_ENABLE='ON'", 514 }, 515 result: "ALTER TABLE `t1` " + 516 "/*T![ttl] TTL = `t` + INTERVAL 1 DAY */ /*T![ttl] TTL_ENABLE = 'OFF' */", 517 }, 518 { 519 event: &model.DDLEvent{ 520 Query: "ALTER TABLE t1 TTL_ENABLE='ON'", 521 }, 522 result: "ALTER TABLE `t1`", 523 }, 524 { 525 event: &model.DDLEvent{ 526 Query: "ALTER TABLE t1 TTL_ENABLE='OFF'", 527 }, 528 result: "ALTER TABLE `t1`", 529 }, 530 { 531 event: &model.DDLEvent{ 532 Query: "ALTER TABLE t1 TTL_JOB_INTERVAL='7h'", 533 }, 534 result: "ALTER TABLE `t1` /*T![ttl] TTL_JOB_INTERVAL = '7h' */", 535 }, 536 { 537 event: &model.DDLEvent{ 538 Query: "alter table t add index j((cast(j->'$.number[*]' as signed array)))", 539 Charset: "utf8", 540 Collate: "utf8_bin", 541 }, 542 result: "ALTER TABLE `t` ADD INDEX `j`((CAST(JSON_EXTRACT(`j`, _UTF8'$.number[*]') " + 543 "AS SIGNED ARRAY)))", 544 }, 545 { 546 event: &model.DDLEvent{ 547 Query: "alter table t add index j((cast(j->'$.number[*]' as signed array)))", 548 }, 549 result: "ALTER TABLE `t` ADD INDEX `j`((CAST(JSON_EXTRACT(`j`, _UTF8MB4'$.number[*]') " + 550 "AS SIGNED ARRAY)))", 551 }, 552 } 553 554 s := &ddlSinkImpl{} 555 s.info = &model.ChangeFeedInfo{ 556 Config: config.GetDefaultReplicaConfig(), 557 } 558 for _, ca := range testCase { 559 re, err := s.addSpecialComment(ca.event) 560 require.Nil(t, err) 561 require.Equal(t, ca.result, re) 562 } 563 _, err := s.addSpecialComment(&model.DDLEvent{ 564 Query: "alter table t force, auto_increment = 12;alter table t force, " + 565 "auto_increment = 12;", 566 }) 567 require.NotNil(t, err) 568 }