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 }