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  }