github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/tests/integration_tests/multi_source/main.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 main 15 16 import ( 17 "context" 18 "database/sql" 19 "flag" 20 "fmt" 21 "os" 22 "reflect" 23 "runtime" 24 "strconv" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/pingcap/errors" 30 "github.com/pingcap/log" 31 "github.com/pingcap/tiflow/tests/integration_tests/util" 32 "go.uber.org/zap" 33 ) 34 35 func main() { 36 cfg := util.NewConfig() 37 err := cfg.Parse(os.Args[1:]) 38 switch errors.Cause(err) { 39 case nil: 40 case flag.ErrHelp: 41 os.Exit(0) 42 default: 43 log.S().Errorf("parse cmd flags err %s\n", err) 44 os.Exit(2) 45 } 46 47 sourceDB0, err := util.CreateDB(cfg.SourceDBCfg[0]) 48 if err != nil { 49 log.S().Fatal(err) 50 } 51 defer func() { 52 if err := util.CloseDB(sourceDB0); err != nil { 53 log.S().Errorf("Failed to close source database: %s\n", err) 54 } 55 }() 56 sourceDB1, err := util.CreateDB(cfg.SourceDBCfg[1]) 57 if err != nil { 58 log.S().Fatal(err) 59 } 60 defer func() { 61 if err := util.CloseDB(sourceDB1); err != nil { 62 log.S().Errorf("Failed to close source database: %s\n", err) 63 } 64 }() 65 ctx, cancel := context.WithCancel(context.Background()) 66 defer cancel() 67 go switchAsyncCommit(ctx, sourceDB0) 68 util.MustExec(sourceDB0, "create database mark;") 69 runDDLTest([]*sql.DB{sourceDB0, sourceDB1}) 70 util.MustExec(sourceDB0, "create table mark.finish_mark(a int primary key);") 71 } 72 73 // for every DDL, run the DDL continuously, and one goroutine for one TiDB instance to do some DML op 74 func runDDLTest(srcs []*sql.DB) { 75 runTime := time.Second * 5 76 start := time.Now() 77 defer func() { 78 log.S().Infof("runDDLTest take %v", time.Since(start)) 79 }() 80 81 for i, ddlFunc := range []func(context.Context, *sql.DB){ 82 createDropSchemaDDL, truncateDDL, addDropColumnDDL, addDropColumnDDL2, 83 modifyColumnDDL, addDropIndexDDL, 84 } { 85 testName := getFunctionName(ddlFunc) 86 log.S().Info("running ddl test: ", i, " ", testName) 87 88 var wg sync.WaitGroup 89 ctx, cancel := context.WithTimeout(context.Background(), runTime) 90 91 for idx, src := range srcs { 92 wg.Add(1) 93 go func(i int, s *sql.DB) { 94 dml(ctx, s, testName, i) 95 wg.Done() 96 }(idx, src) 97 } 98 99 time.Sleep(time.Millisecond) 100 101 wg.Add(1) 102 go func() { 103 ddlFunc(ctx, srcs[0]) 104 wg.Done() 105 }() 106 107 wg.Wait() 108 time.Sleep(5 * time.Second) 109 cancel() 110 111 util.MustExec(srcs[0], fmt.Sprintf("create table mark.finish_mark_%d(a int primary key);", i)) 112 } 113 } 114 115 func switchAsyncCommit(ctx context.Context, db *sql.DB) { 116 ticker := time.NewTicker(5 * time.Second) 117 defer ticker.Stop() 118 enabled := false 119 for { 120 select { 121 case <-ctx.Done(): 122 return 123 case <-ticker.C: 124 if enabled { 125 util.MustExec(db, "set global tidb_enable_async_commit = off") 126 } else { 127 util.MustExec(db, "set global tidb_enable_async_commit = on") 128 } 129 enabled = !enabled 130 } 131 } 132 } 133 134 func getFunctionName(i interface{}) string { 135 strs := strings.Split(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), ".") 136 return strs[len(strs)-1] 137 } 138 139 func createDropSchemaDDL(ctx context.Context, db *sql.DB) { 140 testName := getFunctionName(createDropSchemaDDL) 141 /* 142 mysql> use test; 143 Database changed 144 mysql> create table test1(id int); 145 Query OK, 0 rows affected (0.05 sec) 146 147 mysql> drop database test; 148 Query OK, 3 rows affected (0.02 sec) 149 150 mysql> create database test; 151 Query OK, 1 row affected (0.02 sec) 152 153 mysql> create table test1(id int); 154 ERROR 1046 (3D000): No database selected 155 */ 156 // drop the database used will make the session become No database selected 157 // this make later code use *sql.DB* fail as expected 158 // so we setback the used db before close the conn 159 conn, err := db.Conn(ctx) 160 if err != nil { 161 log.S().Fatal(err) 162 } 163 defer func() { 164 conn.Close() 165 }() 166 167 for { 168 mustCreateTableWithConn(ctx, conn, testName) 169 select { 170 case <-ctx.Done(): 171 return 172 default: 173 } 174 time.Sleep(100 * time.Millisecond) 175 util.MustExecWithConn(ctx, conn, "drop database test") 176 } 177 } 178 179 func truncateDDL(ctx context.Context, db *sql.DB) { 180 testName := getFunctionName(truncateDDL) 181 mustCreateTable(db, testName) 182 183 sql := fmt.Sprintf("truncate table test.`%s`", testName) 184 for { 185 select { 186 case <-ctx.Done(): 187 return 188 default: 189 } 190 util.MustExec(db, sql) 191 time.Sleep(100 * time.Millisecond) 192 } 193 } 194 195 func ignoreableError(err error) bool { 196 knownErrorList := []string{ 197 "Error 1146", // table doesn't exist 198 "Error 1049", // database doesn't exist 199 } 200 for _, e := range knownErrorList { 201 if strings.HasPrefix(err.Error(), e) { 202 return true 203 } 204 } 205 return false 206 } 207 208 func dml(ctx context.Context, db *sql.DB, table string, id int) { 209 var err error 210 var i int 211 var insertSuccess int 212 var deleteSuccess int 213 insertSQL := fmt.Sprintf("insert into test.`%s`(id1, id2) values(?,?)", table) 214 deleteSQL := fmt.Sprintf("delete from test.`%s` where id1 = ? or id2 = ?", table) 215 for i = 0; ; i++ { 216 _, err = db.Exec(insertSQL, i+id*100000000, i+id*100000000+1) 217 if err == nil { 218 insertSuccess++ 219 if insertSuccess%100 == 0 { 220 log.S().Info(id, " insert success: ", insertSuccess) 221 } 222 } 223 if err != nil && !ignoreableError(err) { 224 log.Fatal("unexpected error when executing sql", zap.Error(err)) 225 } 226 227 if i%2 == 0 { 228 result, err := db.Exec(deleteSQL, i+id*100000000, i+id*100000000+1) 229 if err == nil { 230 rows, _ := result.RowsAffected() 231 if rows != 0 { 232 deleteSuccess++ 233 if deleteSuccess%100 == 0 { 234 log.S().Info(id, " delete success: ", deleteSuccess) 235 } 236 } 237 } 238 if err != nil && !ignoreableError(err) { 239 log.Fatal("unexpected error when executing sql", zap.Error(err)) 240 } 241 } 242 243 select { 244 case <-ctx.Done(): 245 return 246 default: 247 } 248 } 249 } 250 251 func addDropColumnDDL(ctx context.Context, db *sql.DB) { 252 testName := getFunctionName(addDropColumnDDL) 253 mustCreateTable(db, testName) 254 255 for value := 1; ; value++ { 256 select { 257 case <-ctx.Done(): 258 return 259 default: 260 } 261 sql := fmt.Sprintf("alter table test.`%s` drop column v1", testName) 262 util.MustExec(db, sql) 263 time.Sleep(100 * time.Millisecond) 264 265 var notNULL string 266 var defaultValue interface{} 267 268 if value%5 == 0 { 269 // use default <value> not null 270 notNULL = "not null" 271 defaultValue = value 272 } else if value%5 == 1 { 273 // use default null 274 defaultValue = nil 275 } else { 276 // use default <value> 277 defaultValue = value 278 } 279 sql = fmt.Sprintf("alter table test.`%s` add column v1 int default ? %s", testName, notNULL) 280 util.MustExec(db, sql, defaultValue) 281 time.Sleep(100 * time.Millisecond) 282 } 283 } 284 285 // addDropColumnDDL2 tests the following scenario: 286 // 1. Create a table, executing DML of this table in background 287 // 2. alter table add column v1 varchar(20) default "xxx" not null 288 // 3. Some rows could have no data for column v1, however when we decode them, 289 // we could use the new table schema (which has the column of v1, caused by 290 // online DDL). Since the column data doesn't exist, the tidb library will 291 // fill in a default value, which is a string type. That's why we can get 292 // either []byte or a string of a column.Value 293 func addDropColumnDDL2(ctx context.Context, db *sql.DB) { 294 testName := getFunctionName(addDropColumnDDL2) 295 mustCreateTable(db, testName) 296 297 for value := 1; ; value++ { 298 select { 299 case <-ctx.Done(): 300 return 301 default: 302 } 303 sql := fmt.Sprintf("alter table test.`%s` drop column v1", testName) 304 util.MustExec(db, sql) 305 time.Sleep(100 * time.Millisecond) 306 307 var notNULL string 308 var defaultValue interface{} 309 310 strValue := strconv.Itoa(value) 311 if value%5 == 0 { 312 // use default <value> 313 defaultValue = strValue 314 } else if value%5 == 1 { 315 // use default null 316 defaultValue = nil 317 } else { 318 // use default <value> not null 319 notNULL = "not null" 320 defaultValue = strValue 321 } 322 sql = fmt.Sprintf("alter table test.`%s` add column v1 varchar(20) default ? %s", testName, notNULL) 323 util.MustExec(db, sql, defaultValue) 324 time.Sleep(100 * time.Millisecond) 325 } 326 } 327 328 func modifyColumnDDL(ctx context.Context, db *sql.DB) { 329 testName := getFunctionName(modifyColumnDDL) 330 mustCreateTable(db, testName) 331 sql := fmt.Sprintf("alter table test.`%s` modify column v1 int default ?", testName) 332 for value := 1; ; value++ { 333 select { 334 case <-ctx.Done(): 335 return 336 default: 337 } 338 339 var defaultValue interface{} 340 // use default null per five modify 341 if value%5 == 0 { 342 defaultValue = nil 343 } else { 344 defaultValue = value 345 } 346 util.MustExec(db, sql, defaultValue) 347 time.Sleep(100 * time.Millisecond) 348 } 349 } 350 351 func addDropIndexDDL(ctx context.Context, db *sql.DB) { 352 testName := getFunctionName(addDropIndexDDL) 353 mustCreateTable(db, testName) 354 355 for value := 1; ; value++ { 356 select { 357 case <-ctx.Done(): 358 return 359 default: 360 } 361 sql := fmt.Sprintf("drop index id1 on test.`%s`;", testName) 362 util.MustExec(db, sql) 363 time.Sleep(100 * time.Millisecond) 364 365 sql = fmt.Sprintf("create unique index `id1` on test.`%s` (id1);", testName) 366 util.MustExec(db, sql) 367 time.Sleep(100 * time.Millisecond) 368 369 sql = fmt.Sprintf("drop index id2 on test.`%s`;", testName) 370 util.MustExec(db, sql) 371 time.Sleep(100 * time.Millisecond) 372 373 sql = fmt.Sprintf("create unique index `id2` on test.`%s` (id2);", testName) 374 util.MustExec(db, sql) 375 time.Sleep(100 * time.Millisecond) 376 } 377 } 378 379 const ( 380 createDatabaseSQL = "create database if not exists test" 381 createTableSQL = ` 382 create table if not exists test.%s 383 ( 384 id1 int unique key not null, 385 id2 int unique key not null, 386 v1 int default null 387 ) 388 ` 389 ) 390 391 func mustCreateTable(db *sql.DB, tableName string) { 392 util.MustExec(db, createDatabaseSQL) 393 sql := fmt.Sprintf(createTableSQL, tableName) 394 util.MustExec(db, sql) 395 } 396 397 func mustCreateTableWithConn(ctx context.Context, conn *sql.Conn, tableName string) { 398 util.MustExecWithConn(ctx, conn, createDatabaseSQL) 399 sql := fmt.Sprintf(createTableSQL, tableName) 400 util.MustExecWithConn(ctx, conn, sql) 401 }