github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/ddlsink/mysql/mysql_ddl_sink_test.go (about) 1 // Copyright 2022 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 mysql 15 16 import ( 17 "context" 18 "database/sql" 19 "net/url" 20 "sync/atomic" 21 "testing" 22 "time" 23 24 "github.com/DATA-DOG/go-sqlmock" 25 dmysql "github.com/go-sql-driver/mysql" 26 "github.com/pingcap/tidb/pkg/infoschema" 27 timodel "github.com/pingcap/tidb/pkg/parser/model" 28 "github.com/pingcap/tiflow/cdc/model" 29 "github.com/pingcap/tiflow/pkg/config" 30 pmysql "github.com/pingcap/tiflow/pkg/sink/mysql" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestWriteDDLEvent(t *testing.T) { 35 dbIndex := 0 36 GetDBConnImpl = func(ctx context.Context, dsnStr string) (*sql.DB, error) { 37 defer func() { 38 dbIndex++ 39 }() 40 if dbIndex == 0 { 41 // test db 42 db, err := pmysql.MockTestDB() 43 require.Nil(t, err) 44 return db, nil 45 } 46 // normal db 47 db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) 48 require.Nil(t, err) 49 mock.ExpectQuery("select tidb_version()"). 50 WillReturnRows(sqlmock.NewRows([]string{"tidb_version()"}).AddRow("5.7.25-TiDB-v4.0.0-beta-191-ga1b3e3b")) 51 mock.ExpectQuery("select tidb_version()"). 52 WillReturnRows(sqlmock.NewRows([]string{"tidb_version()"}).AddRow("5.7.25-TiDB-v4.0.0-beta-191-ga1b3e3b")) 53 mock.ExpectExec("SET SESSION tidb_cdc_write_source = 1").WillReturnResult(sqlmock.NewResult(1, 0)) 54 55 mock.ExpectBegin() 56 mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1)) 57 mock.ExpectExec("SET SESSION tidb_cdc_write_source = 1").WillReturnResult(sqlmock.NewResult(1, 0)) 58 mock.ExpectExec("ALTER TABLE test.t1 ADD COLUMN a int").WillReturnResult(sqlmock.NewResult(1, 1)) 59 mock.ExpectCommit() 60 61 mock.ExpectBegin() 62 mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1)) 63 mock.ExpectExec("SET SESSION tidb_cdc_write_source = 1").WillReturnResult(sqlmock.NewResult(1, 0)) 64 mock.ExpectExec("ALTER TABLE test.t1 ADD COLUMN a int"). 65 WillReturnError(&dmysql.MySQLError{ 66 Number: uint16(infoschema.ErrColumnExists.Code()), 67 }) 68 mock.ExpectRollback() 69 mock.ExpectClose() 70 return db, nil 71 } 72 73 ctx, cancel := context.WithCancel(context.Background()) 74 defer cancel() 75 changefeed := "test-changefeed" 76 sinkURI, err := url.Parse("mysql://127.0.0.1:4000") 77 require.Nil(t, err) 78 rc := config.GetDefaultReplicaConfig() 79 sink, err := NewDDLSink(ctx, model.DefaultChangeFeedID(changefeed), sinkURI, rc) 80 81 require.Nil(t, err) 82 83 ddl1 := &model.DDLEvent{ 84 StartTs: 1000, 85 CommitTs: 1010, 86 TableInfo: &model.TableInfo{ 87 TableName: model.TableName{ 88 Schema: "test", 89 Table: "t1", 90 }, 91 }, 92 Type: timodel.ActionAddColumn, 93 Query: "ALTER TABLE test.t1 ADD COLUMN a int", 94 } 95 err = sink.WriteDDLEvent(ctx, ddl1) 96 require.Nil(t, err) 97 err = sink.WriteDDLEvent(ctx, ddl1) 98 require.Nil(t, err) 99 100 sink.Close() 101 } 102 103 func TestNeedSwitchDB(t *testing.T) { 104 t.Parallel() 105 106 testCases := []struct { 107 ddl *model.DDLEvent 108 needSwitch bool 109 }{ 110 { 111 &model.DDLEvent{ 112 TableInfo: &model.TableInfo{ 113 TableName: model.TableName{ 114 Schema: "", 115 }, 116 }, 117 Type: timodel.ActionCreateTable, 118 }, 119 false, 120 }, 121 { 122 &model.DDLEvent{ 123 TableInfo: &model.TableInfo{ 124 TableName: model.TableName{Schema: "golang"}, 125 }, 126 Type: timodel.ActionCreateSchema, 127 }, 128 false, 129 }, 130 { 131 &model.DDLEvent{ 132 TableInfo: &model.TableInfo{ 133 TableName: model.TableName{Schema: "golang"}, 134 }, 135 Type: timodel.ActionDropSchema, 136 }, 137 false, 138 }, 139 { 140 &model.DDLEvent{ 141 TableInfo: &model.TableInfo{ 142 TableName: model.TableName{Schema: "golang"}, 143 }, 144 Type: timodel.ActionCreateTable, 145 }, 146 true, 147 }, 148 } 149 150 for _, tc := range testCases { 151 require.Equal(t, tc.needSwitch, needSwitchDB(tc.ddl)) 152 } 153 } 154 155 func TestAsyncExecAddIndex(t *testing.T) { 156 ddlExecutionTime := time.Millisecond * 3000 157 var dbIndex int32 = 0 158 GetDBConnImpl = func(ctx context.Context, dsnStr string) (*sql.DB, error) { 159 defer func() { 160 atomic.AddInt32(&dbIndex, 1) 161 }() 162 if atomic.LoadInt32(&dbIndex) == 0 { 163 // test db 164 db, err := pmysql.MockTestDB() 165 require.Nil(t, err) 166 return db, nil 167 } 168 // normal db 169 db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) 170 require.Nil(t, err) 171 mock.ExpectQuery("select tidb_version()"). 172 WillReturnRows(sqlmock.NewRows([]string{"tidb_version()"}).AddRow("5.7.25-TiDB-v4.0.0-beta-191-ga1b3e3b")) 173 mock.ExpectQuery("select tidb_version()").WillReturnError(&dmysql.MySQLError{ 174 Number: 1305, 175 Message: "FUNCTION test.tidb_version does not exist", 176 }) 177 mock.ExpectBegin() 178 mock.ExpectExec("USE `test`;"). 179 WillReturnResult(sqlmock.NewResult(1, 1)) 180 mock.ExpectExec("Create index idx1 on test.t1(a)"). 181 WillDelayFor(ddlExecutionTime). 182 WillReturnResult(sqlmock.NewResult(1, 1)) 183 mock.ExpectCommit() 184 mock.ExpectClose() 185 return db, nil 186 } 187 188 ctx, cancel := context.WithCancel(context.Background()) 189 defer cancel() 190 changefeed := "test-changefeed" 191 sinkURI, err := url.Parse("mysql://127.0.0.1:4000") 192 require.Nil(t, err) 193 rc := config.GetDefaultReplicaConfig() 194 sink, err := NewDDLSink(ctx, model.DefaultChangeFeedID(changefeed), sinkURI, rc) 195 196 require.Nil(t, err) 197 198 ddl1 := &model.DDLEvent{ 199 StartTs: 1000, 200 CommitTs: 1010, 201 TableInfo: &model.TableInfo{ 202 TableName: model.TableName{ 203 Schema: "test", 204 Table: "t1", 205 }, 206 }, 207 Type: timodel.ActionAddIndex, 208 Query: "Create index idx1 on test.t1(a)", 209 } 210 start := time.Now() 211 err = sink.WriteDDLEvent(ctx, ddl1) 212 require.Nil(t, err) 213 require.True(t, time.Since(start) < ddlExecutionTime) 214 require.True(t, time.Since(start) >= 2*time.Second) 215 sink.Close() 216 }