github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/tests/integration_tests/util/db.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 util
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"fmt"
    20  	"net/url"
    21  	"time"
    22  
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	"github.com/pingcap/tidb-tools/pkg/diff"
    26  	"github.com/pingcap/tidb/pkg/util/dbutil"
    27  	"go.uber.org/zap"
    28  )
    29  
    30  // DBConfig is the DB configuration.
    31  type DBConfig struct {
    32  	Host string `toml:"host" json:"host"`
    33  
    34  	User string `toml:"user" json:"user"`
    35  
    36  	Password string `toml:"password" json:"password"`
    37  
    38  	Name string `toml:"name" json:"name"`
    39  
    40  	Port int `toml:"port" json:"port"`
    41  }
    42  
    43  func (c *DBConfig) String() string {
    44  	if c == nil {
    45  		return "<nil>"
    46  	}
    47  	return fmt.Sprintf("DBConfig(%+v)", *c)
    48  }
    49  
    50  // CreateDB create a mysql fd
    51  func CreateDB(cfg DBConfig) (*sql.DB, error) {
    52  	// just set to the same timezone so the timestamp field of mysql will return the same value
    53  	// timestamp field will be display as the time zone of the Local time of drainer when write to kafka, so we set it to local time to pass CI now
    54  	_, offset := time.Now().Zone()
    55  	zone := fmt.Sprintf("'+%02d:00'", offset/3600)
    56  	dbDSN := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&interpolateParams=true&multiStatements=true&time_zone=%s", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name, url.QueryEscape(zone))
    57  	db, err := sql.Open("mysql", dbDSN)
    58  	if err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  
    62  	return db, nil
    63  }
    64  
    65  // CloseDB close the mysql fd
    66  func CloseDB(db *sql.DB) error {
    67  	return errors.Trace(db.Close())
    68  }
    69  
    70  // CloseDBs close the mysql fd
    71  func CloseDBs(dbs []*sql.DB) error {
    72  	for _, db := range dbs {
    73  		err := db.Close()
    74  		if err != nil {
    75  			return errors.Trace(err)
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  // CheckSyncState check if srouceDB and targetDB has the same table and data
    82  func CheckSyncState(sourceDB, targetDB *sql.DB, schema string) bool {
    83  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    84  	defer cancel()
    85  	tables, err := dbutil.GetTables(ctx, sourceDB, schema)
    86  	if err != nil {
    87  		log.Error("get tables", zap.Error(err))
    88  		return false
    89  	}
    90  
    91  	for _, table := range tables {
    92  		sourceTableInstance := &diff.TableInstance{
    93  			Conn:   sourceDB,
    94  			Schema: schema,
    95  			Table:  table,
    96  		}
    97  
    98  		targetTableInstance := &diff.TableInstance{
    99  			Conn:   targetDB,
   100  			Schema: schema,
   101  			Table:  table,
   102  		}
   103  		tableDiff := &diff.TableDiff{
   104  			SourceTables: []*diff.TableInstance{sourceTableInstance},
   105  			TargetTable:  targetTableInstance,
   106  			UseChecksum:  true,
   107  			ChunkSize:    1000,
   108  			CpDB:         targetDB,
   109  		}
   110  		structEqual, dataEqual, err := tableDiff.Equal(context.Background(), func(sql string) error {
   111  			log.Info("check equal", zap.String("sql", sql))
   112  			return nil
   113  		})
   114  		if err != nil {
   115  			log.Error("check equal", zap.String("err", errors.ErrorStack(err)))
   116  			return false
   117  		}
   118  		if !structEqual || !dataEqual {
   119  			return false
   120  		}
   121  	}
   122  
   123  	// check whether the tables in the targetDB is match that in the sourceDB
   124  	targetTables, err := dbutil.GetTables(ctx, targetDB, schema)
   125  	if err != nil {
   126  		log.Error("get tables", zap.Error(err))
   127  		return false
   128  	}
   129  	sourceTableMap := make(map[string]struct{}, len(tables))
   130  	for _, table := range tables {
   131  		sourceTableMap[table] = struct{}{}
   132  	}
   133  	for _, table := range targetTables {
   134  		if _, exist := sourceTableMap[table]; !exist {
   135  			log.Info("The table in target db does not exist in source db", zap.String("table", table))
   136  			return false
   137  		}
   138  	}
   139  
   140  	return true
   141  }
   142  
   143  // MustExec executes sqls
   144  func MustExec(db *sql.DB, sql string, args ...interface{}) {
   145  	_, err := db.Exec(sql, args...)
   146  	if err != nil {
   147  		log.S().Fatalf("exec failed, sql: %s args: %v, err: %+v", sql, args, err)
   148  	}
   149  }
   150  
   151  // MustExecWithConn executes sqls with context
   152  func MustExecWithConn(ctx context.Context, conn *sql.Conn, sql string, args ...interface{}) {
   153  	var err error
   154  	_, err = conn.ExecContext(ctx, sql, args...)
   155  	if err != nil && errors.Cause(err) == context.DeadlineExceeded && errors.Cause(err) == context.Canceled {
   156  		log.S().Fatal(err)
   157  	}
   158  }
   159  
   160  // CreateSourceDBs return source sql.DB for test
   161  // we create two TiDB instance now in tests/integration_tests/run.sh, change it if needed
   162  func CreateSourceDBs() (dbs []*sql.DB, err error) {
   163  	cfg := DBConfig{
   164  		Host:     "127.0.0.1",
   165  		User:     "root",
   166  		Password: "",
   167  		Name:     "test",
   168  		Port:     4000,
   169  	}
   170  
   171  	src1, err := CreateDB(cfg)
   172  	if err != nil {
   173  		return nil, errors.Trace(err)
   174  	}
   175  
   176  	cfg.Port = 4001
   177  	src2, err := CreateDB(cfg)
   178  	if err != nil {
   179  		return nil, errors.Trace(err)
   180  	}
   181  
   182  	dbs = append(dbs, src1, src2)
   183  	return
   184  }
   185  
   186  // CreateSourceDB return source sql.DB for test
   187  func CreateSourceDB() (db *sql.DB, err error) {
   188  	cfg := DBConfig{
   189  		Host:     "127.0.0.1",
   190  		User:     "root",
   191  		Password: "",
   192  		Name:     "test",
   193  		Port:     4000,
   194  	}
   195  
   196  	return CreateDB(cfg)
   197  }
   198  
   199  // CreateSinkDB return sink sql.DB for test
   200  func CreateSinkDB() (db *sql.DB, err error) {
   201  	cfg := DBConfig{
   202  		Host:     "127.0.0.1",
   203  		User:     "root",
   204  		Password: "",
   205  		Name:     "test",
   206  		Port:     3306,
   207  	}
   208  
   209  	return CreateDB(cfg)
   210  }