github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/chaos/cases/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 main 15 16 import ( 17 "context" 18 "database/sql" 19 "fmt" 20 "time" 21 22 "github.com/go-sql-driver/mysql" 23 "github.com/pingcap/errors" 24 "github.com/pingcap/tidb/pkg/errno" 25 "github.com/pingcap/tidb/pkg/util/dbutil" 26 "github.com/pingcap/tiflow/dm/pkg/conn" 27 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 28 "github.com/pingcap/tiflow/dm/pkg/log" 29 "github.com/pingcap/tiflow/dm/pkg/retry" 30 ) 31 32 // dbConn holds a connection to a database and supports to reset the connection. 33 type dbConn struct { 34 baseConn *conn.BaseConn 35 currDB string // current database (will `USE` it when reseting the connection). 36 resetFunc func(ctx context.Context, baseConn *conn.BaseConn) (*conn.BaseConn, error) 37 } 38 39 // createDBConn creates a dbConn instance. 40 func createDBConn(ctx context.Context, db *conn.BaseDB, currDB string) (*dbConn, error) { 41 c, err := db.GetBaseConn(ctx) 42 if err != nil { 43 return nil, err 44 } 45 46 return &dbConn{ 47 baseConn: c, 48 currDB: currDB, 49 resetFunc: func(ctx context.Context, baseConn *conn.BaseConn) (*conn.BaseConn, error) { 50 db.ForceCloseConnWithoutErr(baseConn) 51 return db.GetBaseConn(ctx) 52 }, 53 }, nil 54 } 55 56 // resetConn resets the underlying connection. 57 func (c *dbConn) resetConn(ctx context.Context) error { 58 baseConn, err := c.resetFunc(ctx, c.baseConn) 59 if err != nil { 60 return err 61 } 62 63 _, err = baseConn.ExecuteSQL(tcontext.NewContext(ctx, log.L()), nil, "chaos-cases", []string{fmt.Sprintf("USE %s", c.currDB)}) 64 if err != nil { 65 return err 66 } 67 68 c.baseConn = baseConn 69 return nil 70 } 71 72 // execSQLs executes SQL queries. 73 func (c *dbConn) execSQLs(ctx context.Context, queries ...string) error { 74 params := retry.Params{ 75 RetryCount: 3, 76 FirstRetryDuration: time.Second, 77 BackoffStrategy: retry.Stable, 78 IsRetryableFn: func(_ int, err error) bool { 79 if retry.IsConnectionError(err) || forceIgnoreExecSQLError(err) { 80 // HACK: for some errors like `invalid connection`, `sql: connection is already closed`, we can ignore them just for testing. 81 err = c.resetConn(ctx) 82 return err == nil 83 } 84 return false 85 }, 86 } 87 88 _, _, err := c.baseConn.ApplyRetryStrategy(tcontext.NewContext(ctx, log.L()), params, 89 func(tctx *tcontext.Context) (interface{}, error) { 90 ret, err2 := c.baseConn.ExecuteSQLWithIgnoreError(tctx, nil, "chaos-cases", ignoreExecSQLError, queries) 91 return ret, err2 92 }) 93 return err 94 } 95 96 // execSQLs executes DDL queries. 97 func (c *dbConn) execDDLs(ctx context.Context, queries ...string) error { 98 return c.execSQLs(ctx, queries...) 99 } 100 101 // dropDatabase drops the database if exists. 102 func dropDatabase(ctx context.Context, conn2 *dbConn, name string) error { 103 query := fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbutil.ColumnName(name)) 104 return conn2.execSQLs(ctx, query) 105 } 106 107 // createDatabase creates a database if not exists. 108 func createDatabase(ctx context.Context, conn2 *dbConn, name string) error { 109 query := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s CHARSET latin1", dbutil.ColumnName(name)) 110 return conn2.execSQLs(ctx, query) 111 } 112 113 func ignoreExecSQLError(err error) bool { 114 err = errors.Cause(err) // check the original error 115 mysqlErr, ok := err.(*mysql.MySQLError) 116 if !ok { 117 return false 118 } 119 120 switch mysqlErr.Number { 121 case errno.ErrParse: // HACK: the query generated by `go-sqlsmith` may be invalid, so we just ignore them. 122 return true 123 case errno.ErrDupEntry: // HACK: we tolerate `invalid connection`, then `Duplicate entry` may be reported. 124 return true 125 case errno.ErrTooBigRowsize: // HACK: we tolerate `Error 1118: Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535` 126 return true 127 case errno.ErrCantDropFieldOrKey: // HACK: ignore error `Can't DROP '.*'; check that column/key exists` 128 return true 129 default: 130 return false 131 } 132 } 133 134 // forceIgnoreExecSQLError returns true for some errors which can be ignored ONLY in these tests. 135 func forceIgnoreExecSQLError(err error) bool { 136 err = errors.Cause(err) 137 switch err { 138 case mysql.ErrInvalidConn: 139 return true 140 case sql.ErrConnDone: 141 return true 142 } 143 return false 144 }