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  }