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 }