github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/applier/redo_test.go (about)

     1  // Copyright 2021 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 applier
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"fmt"
    20  	"os"
    21  	"testing"
    22  
    23  	"github.com/DATA-DOG/go-sqlmock"
    24  	"github.com/go-sql-driver/mysql"
    25  	"github.com/phayes/freeport"
    26  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    27  	mysqlParser "github.com/pingcap/tidb/pkg/parser/mysql"
    28  	"github.com/pingcap/tiflow/cdc/model"
    29  	"github.com/pingcap/tiflow/cdc/redo/reader"
    30  	mysqlDDL "github.com/pingcap/tiflow/cdc/sink/ddlsink/mysql"
    31  	"github.com/pingcap/tiflow/cdc/sink/dmlsink/txn"
    32  	pmysql "github.com/pingcap/tiflow/pkg/sink/mysql"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  var _ reader.RedoLogReader = &MockReader{}
    37  
    38  // MockReader is a mock redo log reader that implements LogReader interface
    39  type MockReader struct {
    40  	checkpointTs uint64
    41  	resolvedTs   uint64
    42  	redoLogCh    chan *model.RowChangedEvent
    43  	ddlEventCh   chan *model.DDLEvent
    44  }
    45  
    46  // NewMockReader creates a new MockReader
    47  func NewMockReader(
    48  	checkpointTs uint64,
    49  	resolvedTs uint64,
    50  	redoLogCh chan *model.RowChangedEvent,
    51  	ddlEventCh chan *model.DDLEvent,
    52  ) *MockReader {
    53  	return &MockReader{
    54  		checkpointTs: checkpointTs,
    55  		resolvedTs:   resolvedTs,
    56  		redoLogCh:    redoLogCh,
    57  		ddlEventCh:   ddlEventCh,
    58  	}
    59  }
    60  
    61  // ResetReader implements LogReader.ReadLog
    62  func (br *MockReader) Run(ctx context.Context) error {
    63  	return nil
    64  }
    65  
    66  // ReadNextRow implements LogReader.ReadNextRow
    67  func (br *MockReader) ReadNextRow(ctx context.Context) (*model.RowChangedEvent, error) {
    68  	select {
    69  	case <-ctx.Done():
    70  		return nil, ctx.Err()
    71  	case row := <-br.redoLogCh:
    72  		return row, nil
    73  	}
    74  }
    75  
    76  // ReadNextDDL implements LogReader.ReadNextDDL
    77  func (br *MockReader) ReadNextDDL(ctx context.Context) (*model.DDLEvent, error) {
    78  	select {
    79  	case <-ctx.Done():
    80  		return nil, ctx.Err()
    81  	case ddl := <-br.ddlEventCh:
    82  		return ddl, nil
    83  	}
    84  }
    85  
    86  // ReadMeta implements LogReader.ReadMeta
    87  func (br *MockReader) ReadMeta(ctx context.Context) (checkpointTs, resolvedTs uint64, err error) {
    88  	return br.checkpointTs, br.resolvedTs, nil
    89  }
    90  
    91  func TestApply(t *testing.T) {
    92  	ctx, cancel := context.WithCancel(context.Background())
    93  	defer cancel()
    94  
    95  	checkpointTs := uint64(1000)
    96  	resolvedTs := uint64(2000)
    97  	redoLogCh := make(chan *model.RowChangedEvent, 1024)
    98  	ddlEventCh := make(chan *model.DDLEvent, 1024)
    99  	createMockReader := func(ctx context.Context, cfg *RedoApplierConfig) (reader.RedoLogReader, error) {
   100  		return NewMockReader(checkpointTs, resolvedTs, redoLogCh, ddlEventCh), nil
   101  	}
   102  
   103  	dbIndex := 0
   104  	// DML sink and DDL sink share the same db
   105  	db := getMockDB(t)
   106  	mockGetDBConn := func(ctx context.Context, dsnStr string) (*sql.DB, error) {
   107  		defer func() {
   108  			dbIndex++
   109  		}()
   110  		if dbIndex%2 == 0 {
   111  			testDB, err := pmysql.MockTestDB()
   112  			require.Nil(t, err)
   113  			return testDB, nil
   114  		}
   115  		return db, nil
   116  	}
   117  
   118  	getDMLDBConnBak := txn.GetDBConnImpl
   119  	txn.GetDBConnImpl = mockGetDBConn
   120  	getDDLDBConnBak := mysqlDDL.GetDBConnImpl
   121  	mysqlDDL.GetDBConnImpl = mockGetDBConn
   122  	createRedoReaderBak := createRedoReader
   123  	createRedoReader = createMockReader
   124  	defer func() {
   125  		createRedoReader = createRedoReaderBak
   126  		txn.GetDBConnImpl = getDMLDBConnBak
   127  		mysqlDDL.GetDBConnImpl = getDDLDBConnBak
   128  	}()
   129  
   130  	tableInfo := model.BuildTableInfo("test", "t1", []*model.Column{
   131  		{
   132  			Name: "a",
   133  			Type: mysqlParser.TypeLong,
   134  			Flag: model.HandleKeyFlag | model.PrimaryKeyFlag,
   135  		}, {
   136  			Name: "b",
   137  			Type: mysqlParser.TypeString,
   138  			Flag: 0,
   139  		},
   140  	}, [][]int{{0}})
   141  	dmls := []*model.RowChangedEvent{
   142  		{
   143  			StartTs:   1100,
   144  			CommitTs:  1200,
   145  			TableInfo: tableInfo,
   146  			Columns: model.Columns2ColumnDatas([]*model.Column{
   147  				{
   148  					Name:  "a",
   149  					Value: 1,
   150  				}, {
   151  					Name:  "b",
   152  					Value: "2",
   153  				},
   154  			}, tableInfo),
   155  		},
   156  		// update event which doesn't modify handle key
   157  		{
   158  			StartTs:   1120,
   159  			CommitTs:  1220,
   160  			TableInfo: tableInfo,
   161  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   162  				{
   163  					Name:  "a",
   164  					Value: 1,
   165  				}, {
   166  					Name:  "b",
   167  					Value: "2",
   168  				},
   169  			}, tableInfo),
   170  			Columns: model.Columns2ColumnDatas([]*model.Column{
   171  				{
   172  					Name:  "a",
   173  					Value: 1,
   174  				}, {
   175  					Name:  "b",
   176  					Value: "3",
   177  				},
   178  			}, tableInfo),
   179  		},
   180  		{
   181  			StartTs:   1150,
   182  			CommitTs:  1250,
   183  			TableInfo: tableInfo,
   184  			Columns: model.Columns2ColumnDatas([]*model.Column{
   185  				{
   186  					Name:  "a",
   187  					Value: 10,
   188  				}, {
   189  					Name:  "b",
   190  					Value: "20",
   191  				},
   192  			}, tableInfo),
   193  		},
   194  		{
   195  			StartTs:   1150,
   196  			CommitTs:  1250,
   197  			TableInfo: tableInfo,
   198  			Columns: model.Columns2ColumnDatas([]*model.Column{
   199  				{
   200  					Name:  "a",
   201  					Value: 100,
   202  				}, {
   203  					Name:  "b",
   204  					Value: "200",
   205  				},
   206  			}, tableInfo),
   207  		},
   208  		{
   209  			StartTs:   1200,
   210  			CommitTs:  resolvedTs,
   211  			TableInfo: tableInfo,
   212  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   213  				{
   214  					Name:  "a",
   215  					Value: 10,
   216  				}, {
   217  					Name:  "b",
   218  					Value: "20",
   219  				},
   220  			}, tableInfo),
   221  		},
   222  		{
   223  			StartTs:   1200,
   224  			CommitTs:  resolvedTs,
   225  			TableInfo: tableInfo,
   226  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   227  				{
   228  					Name:  "a",
   229  					Value: 1,
   230  				}, {
   231  					Name:  "b",
   232  					Value: "3",
   233  				},
   234  			}, tableInfo),
   235  			Columns: model.Columns2ColumnDatas([]*model.Column{
   236  				{
   237  					Name:  "a",
   238  					Value: 2,
   239  				}, {
   240  					Name:  "b",
   241  					Value: "3",
   242  				},
   243  			}, tableInfo),
   244  		},
   245  		{
   246  			StartTs:   1200,
   247  			CommitTs:  resolvedTs,
   248  			TableInfo: tableInfo,
   249  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   250  				{
   251  					Name:  "a",
   252  					Value: 100,
   253  				}, {
   254  					Name:  "b",
   255  					Value: "200",
   256  				},
   257  			}, tableInfo),
   258  			Columns: model.Columns2ColumnDatas([]*model.Column{
   259  				{
   260  					Name:  "a",
   261  					Value: 200,
   262  				}, {
   263  					Name:  "b",
   264  					Value: "300",
   265  				},
   266  			}, tableInfo),
   267  		},
   268  	}
   269  	for _, dml := range dmls {
   270  		redoLogCh <- dml
   271  	}
   272  	ddls := []*model.DDLEvent{
   273  		{
   274  			CommitTs: checkpointTs,
   275  			TableInfo: &model.TableInfo{
   276  				TableName: model.TableName{
   277  					Schema: "test", Table: "checkpoint",
   278  				},
   279  			},
   280  			Query: "create table checkpoint(id int)",
   281  			Type:  timodel.ActionCreateTable,
   282  		},
   283  		{
   284  			CommitTs: resolvedTs,
   285  			TableInfo: &model.TableInfo{
   286  				TableName: model.TableName{
   287  					Schema: "test", Table: "resolved",
   288  				},
   289  			},
   290  			Query: "create table resolved(id int not null unique key)",
   291  			Type:  timodel.ActionCreateTable,
   292  		},
   293  	}
   294  	for _, ddl := range ddls {
   295  		ddlEventCh <- ddl
   296  	}
   297  	close(redoLogCh)
   298  	close(ddlEventCh)
   299  
   300  	dir, err := os.Getwd()
   301  	require.Nil(t, err)
   302  	cfg := &RedoApplierConfig{
   303  		SinkURI: "mysql://127.0.0.1:4000/?worker-count=1&max-txn-row=1" +
   304  			"&tidb_placement_mode=ignore&safe-mode=true&cache-prep-stmts=false" +
   305  			"&multi-stmt-enable=false",
   306  		Dir: dir,
   307  	}
   308  	ap := NewRedoApplier(cfg)
   309  	err = ap.Apply(ctx)
   310  	require.Nil(t, err)
   311  }
   312  
   313  func TestApplyBigTxn(t *testing.T) {
   314  	ctx, cancel := context.WithCancel(context.Background())
   315  	defer cancel()
   316  
   317  	checkpointTs := uint64(1000)
   318  	resolvedTs := uint64(2000)
   319  	redoLogCh := make(chan *model.RowChangedEvent, 1024)
   320  	ddlEventCh := make(chan *model.DDLEvent, 1024)
   321  	createMockReader := func(ctx context.Context, cfg *RedoApplierConfig) (reader.RedoLogReader, error) {
   322  		return NewMockReader(checkpointTs, resolvedTs, redoLogCh, ddlEventCh), nil
   323  	}
   324  
   325  	dbIndex := 0
   326  	// DML sink and DDL sink share the same db
   327  	db := getMockDBForBigTxn(t)
   328  	mockGetDBConn := func(ctx context.Context, dsnStr string) (*sql.DB, error) {
   329  		defer func() {
   330  			dbIndex++
   331  		}()
   332  		if dbIndex%2 == 0 {
   333  			testDB, err := pmysql.MockTestDB()
   334  			require.Nil(t, err)
   335  			return testDB, nil
   336  		}
   337  		return db, nil
   338  	}
   339  
   340  	getDMLDBConnBak := txn.GetDBConnImpl
   341  	txn.GetDBConnImpl = mockGetDBConn
   342  	getDDLDBConnBak := mysqlDDL.GetDBConnImpl
   343  	mysqlDDL.GetDBConnImpl = mockGetDBConn
   344  	createRedoReaderBak := createRedoReader
   345  	createRedoReader = createMockReader
   346  	defer func() {
   347  		createRedoReader = createRedoReaderBak
   348  		txn.GetDBConnImpl = getDMLDBConnBak
   349  		mysqlDDL.GetDBConnImpl = getDDLDBConnBak
   350  	}()
   351  
   352  	tableInfo := model.BuildTableInfo("test", "t1", []*model.Column{
   353  		{
   354  			Name: "a",
   355  			Type: mysqlParser.TypeLong,
   356  			Flag: model.HandleKeyFlag | model.PrimaryKeyFlag,
   357  		}, {
   358  			Name: "b",
   359  			Type: mysqlParser.TypeString,
   360  			Flag: 0,
   361  		},
   362  	}, [][]int{{0}})
   363  	dmls := make([]*model.RowChangedEvent, 0)
   364  	// insert some rows
   365  	for i := 1; i <= 100; i++ {
   366  		dml := &model.RowChangedEvent{
   367  			StartTs:   1100,
   368  			CommitTs:  1200,
   369  			TableInfo: tableInfo,
   370  			Columns: model.Columns2ColumnDatas([]*model.Column{
   371  				{
   372  					Name:  "a",
   373  					Value: i,
   374  				}, {
   375  					Name:  "b",
   376  					Value: fmt.Sprintf("%d", i+1),
   377  				},
   378  			}, tableInfo),
   379  		}
   380  		dmls = append(dmls, dml)
   381  	}
   382  	// update
   383  	for i := 1; i <= 100; i++ {
   384  		dml := &model.RowChangedEvent{
   385  			StartTs:   1200,
   386  			CommitTs:  1300,
   387  			TableInfo: tableInfo,
   388  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   389  				{
   390  					Name:  "a",
   391  					Value: i,
   392  				}, {
   393  					Name:  "b",
   394  					Value: fmt.Sprintf("%d", i+1),
   395  				},
   396  			}, tableInfo),
   397  			Columns: model.Columns2ColumnDatas([]*model.Column{
   398  				{
   399  					Name:  "a",
   400  					Value: i * 10,
   401  				}, {
   402  					Name:  "b",
   403  					Value: fmt.Sprintf("%d", i*10+1),
   404  				},
   405  			}, tableInfo),
   406  		}
   407  		dmls = append(dmls, dml)
   408  	}
   409  	// delete and update
   410  	for i := 1; i <= 50; i++ {
   411  		dml := &model.RowChangedEvent{
   412  			StartTs:   1300,
   413  			CommitTs:  resolvedTs,
   414  			TableInfo: tableInfo,
   415  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   416  				{
   417  					Name:  "a",
   418  					Value: i * 10,
   419  				}, {
   420  					Name:  "b",
   421  					Value: fmt.Sprintf("%d", i*10+1),
   422  				},
   423  			}, tableInfo),
   424  		}
   425  		dmls = append(dmls, dml)
   426  	}
   427  	for i := 51; i <= 100; i++ {
   428  		dml := &model.RowChangedEvent{
   429  			StartTs:   1300,
   430  			CommitTs:  resolvedTs,
   431  			TableInfo: tableInfo,
   432  			PreColumns: model.Columns2ColumnDatas([]*model.Column{
   433  				{
   434  					Name:  "a",
   435  					Value: i * 10,
   436  				}, {
   437  					Name:  "b",
   438  					Value: fmt.Sprintf("%d", i*10+1),
   439  				},
   440  			}, tableInfo),
   441  			Columns: model.Columns2ColumnDatas([]*model.Column{
   442  				{
   443  					Name:  "a",
   444  					Value: i * 100,
   445  				}, {
   446  					Name:  "b",
   447  					Value: fmt.Sprintf("%d", i*100+1),
   448  				},
   449  			}, tableInfo),
   450  		}
   451  		dmls = append(dmls, dml)
   452  	}
   453  	for _, dml := range dmls {
   454  		redoLogCh <- dml
   455  	}
   456  	ddls := []*model.DDLEvent{
   457  		{
   458  			CommitTs: checkpointTs,
   459  			TableInfo: &model.TableInfo{
   460  				TableName: model.TableName{
   461  					Schema: "test", Table: "checkpoint",
   462  				},
   463  			},
   464  			Query: "create table checkpoint(id int)",
   465  			Type:  timodel.ActionCreateTable,
   466  		},
   467  		{
   468  			CommitTs: resolvedTs,
   469  			TableInfo: &model.TableInfo{
   470  				TableName: model.TableName{
   471  					Schema: "test", Table: "resolved",
   472  				},
   473  			},
   474  			Query: "create table resolved(id int not null unique key)",
   475  			Type:  timodel.ActionCreateTable,
   476  		},
   477  	}
   478  	for _, ddl := range ddls {
   479  		ddlEventCh <- ddl
   480  	}
   481  	close(redoLogCh)
   482  	close(ddlEventCh)
   483  
   484  	dir, err := os.Getwd()
   485  	require.Nil(t, err)
   486  	cfg := &RedoApplierConfig{
   487  		SinkURI: "mysql://127.0.0.1:4000/?worker-count=1&max-txn-row=1" +
   488  			"&tidb_placement_mode=ignore&safe-mode=true&cache-prep-stmts=false" +
   489  			"&multi-stmt-enable=false",
   490  		Dir: dir,
   491  	}
   492  	ap := NewRedoApplier(cfg)
   493  	err = ap.Apply(ctx)
   494  	require.Nil(t, err)
   495  }
   496  
   497  func TestApplyMeetSinkError(t *testing.T) {
   498  	ctx, cancel := context.WithCancel(context.Background())
   499  	defer cancel()
   500  
   501  	port, err := freeport.GetFreePort()
   502  	require.Nil(t, err)
   503  	cfg := &RedoApplierConfig{
   504  		Storage: "blackhole://",
   505  		SinkURI: fmt.Sprintf("mysql://127.0.0.1:%d/?read-timeout=1s&timeout=1s", port),
   506  	}
   507  	ap := NewRedoApplier(cfg)
   508  	err = ap.Apply(ctx)
   509  	require.Regexp(t, "CDC:ErrMySQLConnectionError", err)
   510  }
   511  
   512  func getMockDB(t *testing.T) *sql.DB {
   513  	// normal db
   514  	db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
   515  	require.Nil(t, err)
   516  
   517  	// Before we write data to downstream, we need to check whether the downstream is TiDB.
   518  	// So we mock a select tidb_version() query.
   519  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   520  		Number:  1305,
   521  		Message: "FUNCTION test.tidb_version does not exist",
   522  	})
   523  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   524  		Number:  1305,
   525  		Message: "FUNCTION test.tidb_version does not exist",
   526  	})
   527  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   528  		Number:  1305,
   529  		Message: "FUNCTION test.tidb_version does not exist",
   530  	})
   531  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   532  		Number:  1305,
   533  		Message: "FUNCTION test.tidb_version does not exist",
   534  	})
   535  
   536  	mock.ExpectBegin()
   537  	mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1))
   538  	mock.ExpectExec("create table checkpoint(id int)").WillReturnResult(sqlmock.NewResult(1, 1))
   539  	mock.ExpectCommit()
   540  
   541  	mock.ExpectBegin()
   542  	mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   543  		WithArgs(1, "2").
   544  		WillReturnResult(sqlmock.NewResult(1, 1))
   545  	mock.ExpectCommit()
   546  
   547  	mock.ExpectBegin()
   548  	mock.ExpectExec("UPDATE `test`.`t1` SET `a` = ?, `b` = ? WHERE `a` = ? LIMIT 1").
   549  		WithArgs(1, "3", 1).
   550  		WillReturnResult(sqlmock.NewResult(1, 1))
   551  	mock.ExpectCommit()
   552  
   553  	mock.ExpectBegin()
   554  	mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   555  		WithArgs(10, "20").
   556  		WillReturnResult(sqlmock.NewResult(1, 1))
   557  	mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   558  		WithArgs(100, "200").
   559  		WillReturnResult(sqlmock.NewResult(1, 1))
   560  	mock.ExpectCommit()
   561  
   562  	// First, apply row which commitTs equal to resolvedTs
   563  	mock.ExpectBegin()
   564  	mock.ExpectExec("DELETE FROM `test`.`t1` WHERE (`a` = ?)").
   565  		WithArgs(10).
   566  		WillReturnResult(sqlmock.NewResult(1, 1))
   567  	mock.ExpectExec("DELETE FROM `test`.`t1` WHERE (`a` = ?)").
   568  		WithArgs(1).
   569  		WillReturnResult(sqlmock.NewResult(1, 1))
   570  	mock.ExpectExec("DELETE FROM `test`.`t1` WHERE (`a` = ?)").
   571  		WithArgs(100).
   572  		WillReturnResult(sqlmock.NewResult(1, 1))
   573  	mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   574  		WithArgs(2, "3").
   575  		WillReturnResult(sqlmock.NewResult(1, 1))
   576  	mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   577  		WithArgs(200, "300").
   578  		WillReturnResult(sqlmock.NewResult(1, 1))
   579  	mock.ExpectCommit()
   580  
   581  	// Then, apply ddl which commitTs equal to resolvedTs
   582  	mock.ExpectBegin()
   583  	mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1))
   584  	mock.ExpectExec("create table resolved(id int not null unique key)").WillReturnResult(sqlmock.NewResult(1, 1))
   585  	mock.ExpectCommit()
   586  
   587  	mock.ExpectClose()
   588  	return db
   589  }
   590  
   591  func getMockDBForBigTxn(t *testing.T) *sql.DB {
   592  	// normal db
   593  	db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
   594  	require.Nil(t, err)
   595  
   596  	// Before we write data to downstream, we need to check whether the downstream is TiDB.
   597  	// So we mock a select tidb_version() query.
   598  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   599  		Number:  1305,
   600  		Message: "FUNCTION test.tidb_version does not exist",
   601  	})
   602  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   603  		Number:  1305,
   604  		Message: "FUNCTION test.tidb_version does not exist",
   605  	})
   606  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   607  		Number:  1305,
   608  		Message: "FUNCTION test.tidb_version does not exist",
   609  	})
   610  	mock.ExpectQuery("select tidb_version()").WillReturnError(&mysql.MySQLError{
   611  		Number:  1305,
   612  		Message: "FUNCTION test.tidb_version does not exist",
   613  	})
   614  
   615  	mock.ExpectBegin()
   616  	mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1))
   617  	mock.ExpectExec("create table checkpoint(id int)").WillReturnResult(sqlmock.NewResult(1, 1))
   618  	mock.ExpectCommit()
   619  
   620  	mock.ExpectBegin()
   621  	for i := 1; i <= 100; i++ {
   622  		mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   623  			WithArgs(i, fmt.Sprintf("%d", i+1)).
   624  			WillReturnResult(sqlmock.NewResult(1, 1))
   625  	}
   626  	mock.ExpectCommit()
   627  
   628  	mock.ExpectBegin()
   629  	for i := 1; i <= 100; i++ {
   630  		mock.ExpectExec("DELETE FROM `test`.`t1` WHERE (`a` = ?)").
   631  			WithArgs(i).
   632  			WillReturnResult(sqlmock.NewResult(1, 1))
   633  	}
   634  	for i := 1; i <= 100; i++ {
   635  		mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   636  			WithArgs(i*10, fmt.Sprintf("%d", i*10+1)).
   637  			WillReturnResult(sqlmock.NewResult(1, 1))
   638  	}
   639  	mock.ExpectCommit()
   640  
   641  	// First, apply row which commitTs equal to resolvedTs
   642  	mock.ExpectBegin()
   643  	for i := 1; i <= 100; i++ {
   644  		mock.ExpectExec("DELETE FROM `test`.`t1` WHERE (`a` = ?)").
   645  			WithArgs(i * 10).
   646  			WillReturnResult(sqlmock.NewResult(1, 1))
   647  	}
   648  	for i := 51; i <= 100; i++ {
   649  		mock.ExpectExec("REPLACE INTO `test`.`t1` (`a`,`b`) VALUES (?,?)").
   650  			WithArgs(i*100, fmt.Sprintf("%d", i*100+1)).
   651  			WillReturnResult(sqlmock.NewResult(1, 1))
   652  	}
   653  	mock.ExpectCommit()
   654  
   655  	// Then, apply ddl which commitTs equal to resolvedTs
   656  	mock.ExpectBegin()
   657  	mock.ExpectExec("USE `test`;").WillReturnResult(sqlmock.NewResult(1, 1))
   658  	mock.ExpectExec("create table resolved(id int not null unique key)").WillReturnResult(sqlmock.NewResult(1, 1))
   659  	mock.ExpectCommit()
   660  
   661  	mock.ExpectClose()
   662  	return db
   663  }