github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/tests/dailytest/case.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 dailytest 15 16 import ( 17 "database/sql" 18 "fmt" 19 "math/rand" 20 "strings" 21 "sync" 22 "time" 23 24 "github.com/pingcap/errors" 25 "github.com/pingcap/log" 26 ) 27 28 var casePKAddDuplicateUK = []string{ 29 ` 30 CREATE TABLE binlog_pk_add_duplicate_uk(id INT PRIMARY KEY, a1 INT); 31 `, 32 ` 33 INSERT INTO binlog_pk_add_duplicate_uk(id, a1) VALUES(1,1),(2,1); 34 `, 35 ` 36 ALTER TABLE binlog_pk_add_duplicate_uk ADD UNIQUE INDEX aidx(a1); 37 `, 38 } 39 40 var casePKAddDuplicateUKClean = []string{ 41 `DROP TABLE binlog_pk_add_duplicate_uk`, 42 } 43 44 var ( 45 caseAlterDatabase = []string{ 46 `CREATE DATABASE to_be_altered CHARACTER SET utf8;`, 47 `ALTER DATABASE to_be_altered CHARACTER SET utf8mb4;`, 48 } 49 caseAlterDatabaseClean = []string{ 50 `DROP DATABASE to_be_altered;`, 51 } 52 ) 53 54 type testRunner struct { 55 src *sql.DB 56 dst *sql.DB 57 schema string 58 } 59 60 func (tr *testRunner) run(test func(*sql.DB)) { 61 RunTest(tr.src, tr.dst, tr.schema, test) 62 } 63 64 func (tr *testRunner) execSQLs(sqls []string) { 65 RunTest(tr.src, tr.dst, tr.schema, func(src *sql.DB) { 66 err := execSQLs(tr.src, sqls) 67 if err != nil { 68 log.S().Fatal(err) 69 } 70 }) 71 } 72 73 // RunCase run some simple test case 74 func RunCase(src *sql.DB, dst *sql.DB, schema string) { 75 tr := &testRunner{src: src, dst: dst, schema: schema} 76 ineligibleTable(tr, src, dst) 77 78 tr.run(caseUpdateWhileAddingCol) 79 tr.execSQLs([]string{"DROP TABLE growing_cols;"}) 80 81 tr.execSQLs(caseAlterDatabase) 82 tr.execSQLs(caseAlterDatabaseClean) 83 84 // run casePKAddDuplicateUK 85 tr.run(func(src *sql.DB) { 86 err := execSQLs(src, casePKAddDuplicateUK) 87 // the add unique index will failed by duplicate entry 88 if err != nil && !strings.Contains(err.Error(), "Duplicate") { 89 log.S().Fatal(err) 90 } 91 }) 92 tr.execSQLs(casePKAddDuplicateUKClean) 93 94 tr.run(caseUpdateWhileDroppingCol) 95 tr.execSQLs([]string{"DROP TABLE many_cols;"}) 96 97 tr.run(caseTblWithGeneratedCol) 98 tr.execSQLs([]string{"DROP TABLE gen_contacts;"}) 99 tr.run(caseCreateView) 100 tr.execSQLs([]string{"DROP TABLE base_for_view;"}) 101 tr.execSQLs([]string{"DROP VIEW view_user_sum;"}) 102 103 // random op on have both pk and uk table 104 var start time.Time 105 tr.run(func(src *sql.DB) { 106 start = time.Now() 107 108 err := updatePKUK(src, 1000) 109 if err != nil { 110 log.S().Fatal(errors.ErrorStack(err)) 111 } 112 }) 113 114 tr.execSQLs([]string{"DROP TABLE pkuk"}) 115 log.S().Info("sync updatePKUK take: ", time.Since(start)) 116 117 // swap unique index value 118 tr.run(func(src *sql.DB) { 119 mustExec(src, "create table uindex(id int primary key, a1 int unique)") 120 121 mustExec(src, "insert into uindex(id, a1) values(1, 10), (2, 20)") 122 123 tx, err := src.Begin() 124 if err != nil { 125 log.S().Fatal(err) 126 } 127 128 _, err = tx.Exec("update uindex set a1 = 30 where id = 1") 129 if err != nil { 130 log.S().Fatal(err) 131 } 132 133 _, err = tx.Exec("update uindex set a1 = 10 where id = 2") 134 if err != nil { 135 log.S().Fatal(err) 136 } 137 138 _, err = tx.Exec("update uindex set a1 = 20 where id = 1") 139 if err != nil { 140 log.S().Fatal(err) 141 } 142 143 err = tx.Commit() 144 if err != nil { 145 log.S().Fatal(err) 146 } 147 }) 148 tr.run(func(src *sql.DB) { 149 mustExec(src, "drop table uindex") 150 }) 151 152 // test big cdc msg 153 tr.run(func(src *sql.DB) { 154 mustExec(src, "create table binlog_big(id int primary key, data longtext);") 155 156 tx, err := src.Begin() 157 if err != nil { 158 log.S().Fatal(err) 159 } 160 // insert 5 * 1M 161 // note limitation of TiDB: https://github.com/pingcap/docs/blob/733a5b0284e70c5b4d22b93a818210a3f6fbb5a0/FAQ.md#the-error-message-transaction-too-large-is-displayed 162 data := make([]byte, 1<<20) 163 for i := 0; i < 5; i++ { 164 _, err = tx.Query("INSERT INTO binlog_big(id, data) VALUES(?, ?);", i, data) 165 if err != nil { 166 log.S().Fatal(err) 167 } 168 } 169 err = tx.Commit() 170 if err != nil { 171 log.S().Fatal(err) 172 } 173 }) 174 tr.execSQLs([]string{"DROP TABLE binlog_big;"}) 175 } 176 177 func ineligibleTable(tr *testRunner, src *sql.DB, dst *sql.DB) { 178 sqls := []string{ 179 "CREATE TABLE ineligible_table1 (uk int UNIQUE null, ncol int);", 180 "CREATE TABLE ineligible_table2 (ncol1 int, ncol2 int);", 181 182 "insert into ineligible_table1 (uk, ncol) values (1,1);", 183 "insert into ineligible_table2 (ncol1, ncol2) values (2,2);", 184 "ALTER TABLE ineligible_table1 ADD COLUMN c1 INT NOT NULL;", 185 "ALTER TABLE ineligible_table2 ADD COLUMN c1 INT NOT NULL;", 186 "insert into ineligible_table1 (uk, ncol, c1) values (null,2,3);", 187 "insert into ineligible_table2 (ncol1, ncol2, c1) values (1,1,3);", 188 189 "CREATE TABLE eligible_table (uk int UNIQUE not null, ncol int);", 190 "insert into eligible_table (uk, ncol) values (1,1);", 191 "insert into eligible_table (uk, ncol) values (2,2);", 192 "ALTER TABLE eligible_table ADD COLUMN c1 INT NOT NULL;", 193 "insert into eligible_table (uk, ncol, c1) values (3,4,5);", 194 } 195 // execute SQL but don't check 196 for _, sql := range sqls { 197 mustExec(src, sql) 198 } 199 200 synced := false 201 TestLoop: 202 for { 203 rows, err := dst.Query("show tables") 204 if err != nil { 205 log.S().Fatalf("exec failed, sql: 'show tables', err: %+v", err) 206 } 207 for rows.Next() { 208 var tableName string 209 err := rows.Scan(&tableName) 210 if err != nil { 211 log.S().Fatalf("scan result set failed, err: %+v", err) 212 } 213 if tableName == "ineligible_table1" || tableName == "ineligible_table2" { 214 log.S().Fatalf("found unexpected table %s", tableName) 215 } 216 if synced { 217 break TestLoop 218 } 219 if tableName == "eligible_table" { 220 synced = true 221 } 222 } 223 } 224 225 // clean up 226 sqls = []string{ 227 "DROP TABLE ineligible_table1;", 228 "DROP TABLE ineligible_table2;", 229 "DROP TABLE eligible_table;", 230 } 231 tr.execSQLs(sqls) 232 } 233 234 func caseUpdateWhileAddingCol(db *sql.DB) { 235 mustExec(db, ` 236 CREATE TABLE growing_cols ( 237 id INT AUTO_INCREMENT PRIMARY KEY, 238 val INT DEFAULT 0 239 );`) 240 241 var wg sync.WaitGroup 242 243 wg.Add(1) 244 go func() { 245 defer wg.Done() 246 insertSQL := `INSERT INTO growing_cols(id, val) VALUES (?, ?);` 247 mustExec(db, insertSQL, 1, 0) 248 249 // Keep updating to generate DMLs while the other goroutine's adding columns 250 updateSQL := `UPDATE growing_cols SET val = ? WHERE id = ?;` 251 for i := 0; i < 256; i++ { 252 mustExec(db, updateSQL, i, 1) 253 } 254 }() 255 256 wg.Add(1) 257 go func() { 258 defer wg.Done() 259 for i := 0; i < 32; i++ { 260 updateSQL := fmt.Sprintf(`ALTER TABLE growing_cols ADD COLUMN col%d VARCHAR(50);`, i) 261 mustExec(db, updateSQL) 262 } 263 }() 264 265 wg.Wait() 266 } 267 268 func caseUpdateWhileDroppingCol(db *sql.DB) { 269 const nCols = 10 270 var builder strings.Builder 271 for i := 0; i < nCols; i++ { 272 if i != 0 { 273 builder.WriteRune(',') 274 } 275 builder.WriteString(fmt.Sprintf("col%d VARCHAR(50) NOT NULL", i)) 276 } 277 createSQL := fmt.Sprintf(` 278 CREATE TABLE many_cols ( 279 id INT AUTO_INCREMENT PRIMARY KEY, 280 val INT DEFAULT 0, 281 %s 282 );`, builder.String()) 283 mustExec(db, createSQL) 284 285 builder.Reset() 286 for i := 0; i < nCols; i++ { 287 if i != 0 { 288 builder.WriteRune(',') 289 } 290 builder.WriteString(fmt.Sprintf("col%d", i)) 291 } 292 cols := builder.String() 293 294 builder.Reset() 295 for i := 0; i < nCols; i++ { 296 if i != 0 { 297 builder.WriteRune(',') 298 } 299 builder.WriteString(`""`) 300 } 301 placeholders := builder.String() 302 303 // Insert a row with all columns set to empty string 304 insertSQL := fmt.Sprintf(`INSERT INTO many_cols(id, %s) VALUES (?, %s);`, cols, placeholders) 305 mustExec(db, insertSQL, 1) 306 307 closeCh := make(chan struct{}) 308 go func() { 309 // Keep updating to generate DMLs while the other goroutine's dropping columns 310 updateSQL := `UPDATE many_cols SET val = ? WHERE id = ?;` 311 for i := 0; ; i++ { 312 mustExec(db, updateSQL, i, 1) 313 select { 314 case <-closeCh: 315 return 316 default: 317 } 318 } 319 }() 320 321 for i := 0; i < nCols; i++ { 322 mustExec(db, fmt.Sprintf("ALTER TABLE many_cols DROP COLUMN col%d;", i)) 323 } 324 close(closeCh) 325 } 326 327 // caseTblWithGeneratedCol creates a table with generated column, 328 // and insert values into the table 329 func caseTblWithGeneratedCol(db *sql.DB) { 330 mustExec(db, ` 331 CREATE TABLE gen_contacts ( 332 id INT AUTO_INCREMENT PRIMARY KEY, 333 first_name VARCHAR(50) NOT NULL, 334 last_name VARCHAR(50) NOT NULL, 335 other VARCHAR(101), 336 fullname VARCHAR(101) GENERATED ALWAYS AS (CONCAT(first_name,' ',last_name)), 337 initial VARCHAR(101) GENERATED ALWAYS AS (CONCAT(LEFT(first_name, 1),' ',LEFT(last_name,1))) STORED 338 );`) 339 340 insertSQL := "INSERT INTO gen_contacts(first_name, last_name) VALUES(?, ?);" 341 updateSQL := "UPDATE gen_contacts SET other = fullname WHERE first_name = ?" 342 for i := 0; i < 64; i++ { 343 mustExec(db, insertSQL, fmt.Sprintf("John%d", i), fmt.Sprintf("Dow%d", i)) 344 345 idxToUpdate := rand.Intn(i + 1) 346 mustExec(db, updateSQL, fmt.Sprintf("John%d", idxToUpdate)) 347 } 348 delSQL := "DELETE FROM gen_contacts WHERE fullname = ?" 349 for i := 0; i < 10; i++ { 350 mustExec(db, delSQL, fmt.Sprintf("John%d Dow%d", i, i)) 351 } 352 } 353 354 func caseCreateView(db *sql.DB) { 355 mustExec(db, ` 356 CREATE TABLE base_for_view ( 357 id INT AUTO_INCREMENT PRIMARY KEY, 358 user_id INT NOT NULL, 359 amount INT NOT NULL 360 );`) 361 362 mustExec(db, ` 363 CREATE VIEW view_user_sum (user_id, total) 364 AS SELECT user_id, SUM(amount) FROM base_for_view GROUP BY user_id;`) 365 366 insertSQL := "INSERT INTO base_for_view(user_id, amount) VALUES(?, ?);" 367 updateSQL := "UPDATE base_for_view SET amount = ? WHERE user_id = ?;" 368 deleteSQL := "DELETE FROM base_for_view WHERE user_id = ? AND amount = ?;" 369 for i := 0; i < 42; i++ { 370 for j := 0; j < 3; j++ { 371 mustExec(db, insertSQL, i, j*10+i) 372 if i%2 == 0 && j == 1 { 373 mustExec(db, updateSQL, 1111, i) 374 } 375 } 376 } 377 for i := 0; i < 10; i++ { 378 mustExec(db, deleteSQL, i, 1111) 379 } 380 } 381 382 // updatePKUK create a table with primary key and unique key 383 // then do opNum randomly DML 384 func updatePKUK(db *sql.DB, opNum int) error { 385 maxKey := 20 386 mustExec(db, "create table pkuk(pk int primary key, uk int, v int, unique key uk(uk));") 387 388 pks := make(map[int]struct{}) 389 freePks := rand.Perm(maxKey) 390 391 nextPk := func() int { 392 rand.Shuffle(len(freePks), func(i, j int) { 393 freePks[i], freePks[j] = freePks[j], freePks[i] 394 }) 395 return freePks[0] 396 } 397 addPK := func(pk int) { 398 pks[pk] = struct{}{} 399 var i, v int 400 for i, v = range freePks { 401 if v == pk { 402 break 403 } 404 } 405 freePks = append(freePks[:i], freePks[i+1:]...) 406 } 407 removePK := func(pk int) { 408 delete(pks, pk) 409 freePks = append(freePks, pk) 410 } 411 genOldPk := func() int { 412 n := rand.Intn(len(pks)) 413 var i, pk int 414 for pk = range pks { 415 if i == n { 416 break 417 } 418 i++ 419 } 420 return pk 421 } 422 423 for i := 0; i < opNum; { 424 var ( 425 sql string 426 pk, oldPK int 427 ) 428 429 // try randomly insert&update&delete 430 op := rand.Intn(3) 431 switch op { 432 case 0: 433 if len(pks) == maxKey { 434 continue 435 } 436 pk = nextPk() 437 uk := rand.Intn(maxKey) 438 v := rand.Intn(10000) 439 sql = fmt.Sprintf("insert into pkuk(pk, uk, v) values(%d,%d,%d)", pk, uk, v) 440 case 1: 441 if len(pks) == 0 || len(pks) == maxKey { 442 continue 443 } 444 pk = nextPk() 445 oldPK = genOldPk() 446 uk := rand.Intn(maxKey) 447 v := rand.Intn(10000) 448 sql = fmt.Sprintf("update pkuk set pk = %d, uk = %d, v = %d where pk = %d", pk, uk, v, oldPK) 449 case 2: 450 if len(pks) == 0 { 451 continue 452 } 453 oldPK = genOldPk() 454 sql = fmt.Sprintf("delete from pkuk where pk = %d", oldPK) 455 } 456 457 _, err := db.Exec(sql) 458 if err != nil { 459 // for insert and update, we didn't check for uk's duplicate 460 if strings.Contains(err.Error(), "Duplicate entry") { 461 continue 462 } 463 return errors.Trace(err) 464 } 465 466 switch op { 467 case 0: 468 addPK(pk) 469 case 1: 470 removePK(oldPK) 471 addPK(pk) 472 case 2: 473 removePK(oldPK) 474 } 475 i++ 476 } 477 return nil 478 } 479 480 func mustExec(db *sql.DB, sql string, args ...interface{}) { 481 _, err := db.Exec(sql, args...) 482 if err != nil { 483 log.S().Fatalf("exec failed, sql: %s args: %v, err: %+v", sql, args, err) 484 } 485 }