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  }