github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/conn/baseconn_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 conn 15 16 import ( 17 "errors" 18 "testing" 19 20 "github.com/DATA-DOG/go-sqlmock" 21 "github.com/go-sql-driver/mysql" 22 "github.com/pingcap/tidb/pkg/errno" 23 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 24 "github.com/pingcap/tiflow/dm/pkg/retry" 25 "github.com/pingcap/tiflow/dm/pkg/terror" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestBaseConn(t *testing.T) { 30 baseConn := NewBaseConnForTest(nil, nil) 31 32 tctx := tcontext.Background() 33 err := baseConn.SetRetryStrategy(nil) 34 require.NoError(t, err) 35 36 // nolint:sqlclosecheck,rowserrcheck 37 _, err = baseConn.QuerySQL(tctx, "select 1") 38 require.True(t, terror.ErrDBUnExpect.Equal(err)) 39 40 _, err = baseConn.ExecuteSQL(tctx, nil, "test", []string{""}) 41 require.True(t, terror.ErrDBUnExpect.Equal(err)) 42 43 db, mock, err := sqlmock.New() 44 require.NoError(t, err) 45 dbConn, err := db.Conn(tctx.Context()) 46 require.NoError(t, err) 47 48 baseConn = &BaseConn{dbConn, terror.ScopeNotSet, nil} 49 50 err = baseConn.SetRetryStrategy(&retry.FiniteRetryStrategy{}) 51 require.NoError(t, err) 52 53 mock.ExpectQuery("select 1").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) 54 // nolint:sqlclosecheck,rowserrcheck 55 rows, err := baseConn.QuerySQL(tctx, "select 1") 56 require.NoError(t, err) 57 ids := make([]int, 0, 1) 58 for rows.Next() { 59 var id int 60 err = rows.Scan(&id) 61 require.NoError(t, err) 62 ids = append(ids, id) 63 } 64 require.Equal(t, []int{1}, ids) 65 66 mock.ExpectQuery("select 1").WillReturnError(errors.New("invalid connection")) 67 // nolint:sqlclosecheck,rowserrcheck 68 _, err = baseConn.QuerySQL(tctx, "select 1") 69 require.True(t, terror.ErrDBQueryFailed.Equal(err)) 70 71 affected, err := baseConn.ExecuteSQL(tctx, nil, "test", []string{}) 72 require.NoError(t, err) 73 require.Equal(t, 0, affected) 74 75 mock.ExpectBegin() 76 mock.ExpectExec("create database test").WillReturnResult(sqlmock.NewResult(1, 1)) 77 mock.ExpectCommit() 78 affected, err = baseConn.ExecuteSQL(tctx, nil, "test", []string{"create database test"}) 79 require.NoError(t, err) 80 require.Equal(t, 1, affected) 81 82 mock.ExpectBegin().WillReturnError(errors.New("begin error")) 83 _, err = baseConn.ExecuteSQL(tctx, nil, "test", []string{"create database test"}) 84 require.True(t, terror.ErrDBExecuteFailed.Equal(err)) 85 86 mock.ExpectBegin() 87 mock.ExpectExec("create database test").WillReturnError(errors.New("invalid connection")) 88 mock.ExpectRollback() 89 _, err = baseConn.ExecuteSQL(tctx, nil, "test", []string{"create database test"}) 90 require.True(t, terror.ErrDBExecuteFailed.Equal(err)) 91 92 mock.ExpectBegin() 93 mock.ExpectExec("create database test").WillReturnError(errors.New("ignore me")) 94 mock.ExpectExec("create database test").WillReturnError(errors.New("don't ignore me")) 95 mock.ExpectRollback() 96 ignoreF := func(err error) bool { 97 return err.Error() == "ignore me" 98 } 99 affected, err = baseConn.ExecuteSQLWithIgnoreError(tctx, nil, "test", ignoreF, []string{"create database test", "create database test"}) 100 require.Contains(t, err.Error(), "don't ignore me") 101 require.Equal(t, 1, affected) 102 103 require.NoError(t, mock.ExpectationsWereMet()) 104 require.NoError(t, baseConn.forceClose()) 105 } 106 107 func TestAutoSplit4TxnTooLarge(t *testing.T) { 108 tctx := tcontext.Background() 109 db, mock, err := sqlmock.New() 110 require.NoError(t, err) 111 dbConn, err := db.Conn(tctx.Context()) 112 require.NoError(t, err) 113 114 baseConn := &BaseConn{dbConn, terror.ScopeNotSet, nil} 115 116 errTxnTooLarge := &mysql.MySQLError{ 117 Number: errno.ErrTxnTooLarge, 118 Message: "Transaction is too large, size: 123456", 119 } 120 dml := "some DML" 121 122 mockTxn := func(size int) { 123 mock.ExpectBegin() 124 for i := 0; i < size; i++ { 125 mock.ExpectExec(dml).WillReturnResult(sqlmock.NewResult(1, 1)) 126 } 127 if size == 1 { 128 mock.ExpectCommit() 129 } else { 130 mock.ExpectCommit().WillReturnError(errTxnTooLarge) 131 } 132 } 133 134 // we test a transaction of 3 statements 135 mockTxn(3) // [1,2,3] 136 mockTxn(1) // [1] 137 mockTxn(2) // [2,3] 138 mockTxn(1) // [2] 139 mockTxn(1) // [3] 140 txn := []string{dml, dml, dml} 141 args := [][]interface{}{nil, nil, nil} 142 143 err = baseConn.ExecuteSQLsAutoSplit(tctx, nil, "test", txn, args...) 144 require.NoError(t, err) 145 146 mockTxnAlwaysError := func(size int) { 147 mock.ExpectBegin() 148 for i := 0; i < size; i++ { 149 mock.ExpectExec(dml).WillReturnResult(sqlmock.NewResult(1, 1)) 150 } 151 mock.ExpectCommit().WillReturnError(errTxnTooLarge) 152 } 153 154 mockTxnAlwaysError(3) 155 mockTxnAlwaysError(1) 156 157 err = baseConn.ExecuteSQLsAutoSplit(tctx, nil, "test", txn, args...) 158 require.ErrorContains(t, err, "Transaction is too large") 159 160 require.NoError(t, mock.ExpectationsWereMet()) 161 }