github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/error_test.go (about)

     1  // Copyright 2019 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 syncer
    15  
    16  import (
    17  	"context"
    18  	"testing"
    19  
    20  	"github.com/DATA-DOG/go-sqlmock"
    21  	"github.com/go-sql-driver/mysql"
    22  	"github.com/pingcap/errors"
    23  	"github.com/pingcap/tidb/pkg/errno"
    24  	"github.com/pingcap/tiflow/dm/pkg/conn"
    25  	tcontext "github.com/pingcap/tiflow/dm/pkg/context"
    26  	"github.com/pingcap/tiflow/dm/syncer/dbconn"
    27  	"github.com/pingcap/tiflow/dm/syncer/metrics"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func newMysqlErr(number uint16, message string) *mysql.MySQLError {
    32  	return &mysql.MySQLError{
    33  		Number:  number,
    34  		Message: message,
    35  	}
    36  }
    37  
    38  func TestHandleSpecialDDLError(t *testing.T) {
    39  	var (
    40  		cfg                 = genDefaultSubTaskConfig4Test()
    41  		syncer              = NewSyncer(cfg, nil, nil)
    42  		tctx                = tcontext.Background()
    43  		conn2               = dbconn.NewDBConn(cfg, nil)
    44  		customErr           = errors.New("custom error")
    45  		invalidDDL          = "SQL CAN NOT BE PARSED"
    46  		insertDML           = "INSERT INTO tbl VALUES (1)"
    47  		createTable         = "CREATE TABLE tbl (col INT)"
    48  		addUK               = "ALTER TABLE tbl ADD UNIQUE INDEX idx(col)"
    49  		addFK               = "ALTER TABLE tbl ADD CONSTRAINT fk FOREIGN KEY (col) REFERENCES tbl2 (col)"
    50  		addColumn           = "ALTER TABLE tbl ADD COLUMN col INT"
    51  		addIndexMulti       = "ALTER TABLE tbl ADD INDEX idx1(col1), ADD INDEX idx2(col2)"
    52  		addIndex1           = "ALTER TABLE tbl ADD INDEX idx(col)"
    53  		addIndex2           = "CREATE INDEX idx ON tbl(col)"
    54  		dropColumnWithIndex = "ALTER TABLE tbl DROP c1"
    55  		cases               = []struct {
    56  			err     error
    57  			ddls    []string
    58  			index   int
    59  			handled bool
    60  		}{
    61  			{
    62  				err: mysql.ErrInvalidConn, // empty DDLs
    63  			},
    64  			{
    65  				err:  mysql.ErrInvalidConn,
    66  				ddls: []string{addColumn, addIndex1}, // error happen not on the last
    67  			},
    68  			{
    69  				err:  mysql.ErrInvalidConn,
    70  				ddls: []string{addIndex1, addColumn}, // error happen not on the last
    71  			},
    72  			{
    73  				err:  mysql.ErrInvalidConn,
    74  				ddls: []string{addIndex1, addIndex2}, // error happen not on the last
    75  			},
    76  			{
    77  				err:  customErr, // not `invalid connection`
    78  				ddls: []string{addIndex1},
    79  			},
    80  			{
    81  				err:  mysql.ErrInvalidConn,
    82  				ddls: []string{invalidDDL}, // invalid DDL
    83  			},
    84  			{
    85  				err:  mysql.ErrInvalidConn,
    86  				ddls: []string{insertDML}, // invalid DDL
    87  			},
    88  			{
    89  				err:  mysql.ErrInvalidConn,
    90  				ddls: []string{createTable}, // not `ADD INDEX`
    91  			},
    92  			{
    93  				err:  mysql.ErrInvalidConn,
    94  				ddls: []string{addColumn}, // not `ADD INDEX`
    95  			},
    96  			{
    97  				err:  mysql.ErrInvalidConn,
    98  				ddls: []string{addUK}, // not `ADD INDEX`, but `ADD UNIQUE INDEX`
    99  			},
   100  			{
   101  				err:  mysql.ErrInvalidConn,
   102  				ddls: []string{addFK}, // not `ADD INDEX`, but `ADD * FOREIGN KEY`
   103  			},
   104  			{
   105  				err:  mysql.ErrInvalidConn,
   106  				ddls: []string{addIndexMulti}, // multi `ADD INDEX` in one statement
   107  			},
   108  			{
   109  				err:     mysql.ErrInvalidConn,
   110  				ddls:    []string{addIndex1},
   111  				handled: true,
   112  			},
   113  			{
   114  				err:     mysql.ErrInvalidConn,
   115  				ddls:    []string{addIndex2},
   116  				handled: true,
   117  			},
   118  			{
   119  				err:     mysql.ErrInvalidConn,
   120  				ddls:    []string{addColumn, addIndex1},
   121  				index:   1,
   122  				handled: true,
   123  			},
   124  			{
   125  				err:     mysql.ErrInvalidConn,
   126  				ddls:    []string{addColumn, addIndex2},
   127  				index:   1,
   128  				handled: true,
   129  			},
   130  			{
   131  				err:     mysql.ErrInvalidConn,
   132  				ddls:    []string{addIndex1, addIndex2},
   133  				index:   1,
   134  				handled: true,
   135  			},
   136  			{
   137  				err:   newMysqlErr(errno.ErrUnsupportedDDLOperation, "drop column xx with index"),
   138  				ddls:  []string{addIndex1, dropColumnWithIndex},
   139  				index: 0, // wrong index
   140  			},
   141  		}
   142  	)
   143  	conn2.ResetBaseConnFn = func(*tcontext.Context, *conn.BaseConn) (*conn.BaseConn, error) {
   144  		return nil, nil
   145  	}
   146  
   147  	syncer.metricsProxies = metrics.DefaultMetricsProxies.CacheForOneTask("task", "worker", "source")
   148  
   149  	for _, cs := range cases {
   150  		err2 := syncer.handleSpecialDDLError(tctx, cs.err, cs.ddls, cs.index, conn2, -1)
   151  		if cs.handled {
   152  			require.NoError(t, err2)
   153  		} else {
   154  			require.Equal(t, cs.err, err2)
   155  		}
   156  	}
   157  
   158  	var (
   159  		execErr = newMysqlErr(errno.ErrUnsupportedDDLOperation, "drop column xx with index")
   160  		ddls    = []string{dropColumnWithIndex}
   161  	)
   162  
   163  	db, mock, err := sqlmock.New()
   164  	require.NoError(t, err)
   165  	conn1, err := db.Conn(context.Background())
   166  	require.NoError(t, err)
   167  	conn2.ResetBaseConnFn = func(_ *tcontext.Context, _ *conn.BaseConn) (*conn.BaseConn, error) {
   168  		return conn.NewBaseConnForTest(conn1, nil), nil
   169  	}
   170  	err = conn2.ResetConn(tctx)
   171  	require.NoError(t, err)
   172  
   173  	// dropColumnF test successful
   174  	mock.ExpectQuery("SELECT INDEX_NAME FROM information_schema.statistics WHERE.*").WillReturnRows(
   175  		sqlmock.NewRows([]string{"INDEX_NAME"}).AddRow("gen_idx"))
   176  	mock.ExpectQuery("SELECT count\\(\\*\\) FROM information_schema.statistics WHERE.*").WillReturnRows(
   177  		sqlmock.NewRows([]string{"count(*)"}).AddRow(1))
   178  	mock.ExpectBegin()
   179  	mock.ExpectExec("ALTER TABLE ``.`tbl` DROP INDEX `gen_idx`").WillReturnResult(sqlmock.NewResult(0, 1))
   180  	mock.ExpectCommit()
   181  	mock.ExpectBegin()
   182  	mock.ExpectExec(dropColumnWithIndex).WillReturnResult(sqlmock.NewResult(0, 1))
   183  	mock.ExpectCommit()
   184  
   185  	handledErr := syncer.handleSpecialDDLError(tctx, execErr, ddls, 0, conn2, -1)
   186  	require.NoError(t, mock.ExpectationsWereMet())
   187  	require.NoError(t, handledErr)
   188  
   189  	// dropColumnF test failed because multi-column index
   190  	mock.ExpectQuery("SELECT INDEX_NAME FROM information_schema.statistics WHERE.*").WillReturnRows(
   191  		sqlmock.NewRows([]string{"INDEX_NAME"}).AddRow("gen_idx"))
   192  	mock.ExpectQuery("SELECT count\\(\\*\\) FROM information_schema.statistics WHERE.*").WillReturnRows(
   193  		sqlmock.NewRows([]string{"count(*)"}).AddRow(2))
   194  
   195  	handledErr = syncer.handleSpecialDDLError(tctx, execErr, ddls, 0, conn2, -1)
   196  	require.NoError(t, mock.ExpectationsWereMet())
   197  	require.Error(t, execErr, handledErr)
   198  }
   199  
   200  func TestIsConnectionRefusedError(t *testing.T) {
   201  	isConnRefusedErr := isConnectionRefusedError(nil)
   202  	require.False(t, isConnRefusedErr)
   203  
   204  	isConnRefusedErr = isConnectionRefusedError(errors.New("timeout"))
   205  	require.False(t, isConnRefusedErr)
   206  
   207  	isConnRefusedErr = isConnectionRefusedError(errors.New("connect: connection refused"))
   208  	require.True(t, isConnRefusedErr)
   209  }