github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/tests/integration_tests/cdc/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  }