github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/checkpoint_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  	"encoding/json"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/DATA-DOG/go-sqlmock"
    26  	"github.com/go-mysql-org/go-mysql/mysql"
    27  	. "github.com/pingcap/check"
    28  	"github.com/pingcap/log"
    29  	tidbddl "github.com/pingcap/tidb/pkg/ddl"
    30  	"github.com/pingcap/tidb/pkg/parser/ast"
    31  	"github.com/pingcap/tidb/pkg/util/dbutil"
    32  	"github.com/pingcap/tidb/pkg/util/filter"
    33  	"github.com/pingcap/tiflow/dm/config"
    34  	"github.com/pingcap/tiflow/dm/pkg/binlog"
    35  	"github.com/pingcap/tiflow/dm/pkg/conn"
    36  	tcontext "github.com/pingcap/tiflow/dm/pkg/context"
    37  	"github.com/pingcap/tiflow/dm/pkg/cputil"
    38  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    39  	dlog "github.com/pingcap/tiflow/dm/pkg/log"
    40  	"github.com/pingcap/tiflow/dm/pkg/retry"
    41  	"github.com/pingcap/tiflow/dm/pkg/schema"
    42  	"github.com/pingcap/tiflow/dm/syncer/dbconn"
    43  	"github.com/stretchr/testify/require"
    44  	"go.uber.org/zap/zapcore"
    45  )
    46  
    47  var (
    48  	cpid                 = "test_for_db"
    49  	schemaCreateSQL      = ""
    50  	tableCreateSQL       = ""
    51  	clearCheckPointSQL   = ""
    52  	loadCheckPointSQL    = ""
    53  	flushCheckPointSQL   = ""
    54  	deleteCheckPointSQL  = ""
    55  	deleteSchemaPointSQL = ""
    56  )
    57  
    58  var _ = Suite(&testCheckpointSuite{})
    59  
    60  type testCheckpointSuite struct {
    61  	cfg     *config.SubTaskConfig
    62  	mock    sqlmock.Sqlmock
    63  	tracker *schema.Tracker
    64  }
    65  
    66  func (s *testCheckpointSuite) SetUpSuite(c *C) {
    67  	s.cfg = &config.SubTaskConfig{
    68  		ServerID:   101,
    69  		MetaSchema: "test",
    70  		Name:       "syncer_checkpoint_ut",
    71  		Flavor:     mysql.MySQLFlavor,
    72  	}
    73  
    74  	log.SetLevel(zapcore.ErrorLevel)
    75  	var err error
    76  
    77  	s.tracker, err = schema.NewTestTracker(context.Background(), s.cfg.Name, nil, dlog.L())
    78  	c.Assert(err, IsNil)
    79  }
    80  
    81  func (s *testCheckpointSuite) TestUpTest(c *C) {
    82  	s.tracker.Reset()
    83  }
    84  
    85  func (s *testCheckpointSuite) prepareCheckPointSQL() {
    86  	schemaCreateSQL = fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS `%s`", s.cfg.MetaSchema)
    87  	tableCreateSQL = fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s`.`%s` .*", s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name))
    88  	flushCheckPointSQL = fmt.Sprintf("INSERT INTO `%s`.`%s` .* VALUES.* ON DUPLICATE KEY UPDATE .*", s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name))
    89  	clearCheckPointSQL = fmt.Sprintf("DELETE FROM `%s`.`%s` WHERE id = \\?", s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name))
    90  	loadCheckPointSQL = fmt.Sprintf("SELECT .* FROM `%s`.`%s` WHERE id = \\?", s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name))
    91  	deleteCheckPointSQL = fmt.Sprintf("DELETE FROM %s WHERE id = \\? AND cp_schema = \\? AND cp_table = \\?", dbutil.TableName(s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name)))
    92  	deleteSchemaPointSQL = fmt.Sprintf("DELETE FROM %s WHERE id = \\? AND cp_schema = \\?", dbutil.TableName(s.cfg.MetaSchema, cputil.SyncerCheckpoint(s.cfg.Name)))
    93  }
    94  
    95  // this test case uses sqlmock to simulate all SQL operations in tests.
    96  func (s *testCheckpointSuite) TestCheckPoint(c *C) {
    97  	tctx := tcontext.Background()
    98  
    99  	cp := NewRemoteCheckPoint(tctx, s.cfg, nil, cpid)
   100  	defer func() {
   101  		s.mock.ExpectClose()
   102  		cp.Close()
   103  	}()
   104  
   105  	var err error
   106  	db, mock, err := sqlmock.New()
   107  	c.Assert(err, IsNil)
   108  	s.mock = mock
   109  
   110  	s.prepareCheckPointSQL()
   111  
   112  	mock.ExpectBegin()
   113  	mock.ExpectExec(schemaCreateSQL).WillReturnResult(sqlmock.NewResult(0, 1))
   114  	mock.ExpectCommit()
   115  	mock.ExpectBegin()
   116  	mock.ExpectExec(tableCreateSQL).WillReturnResult(sqlmock.NewResult(0, 1))
   117  	mock.ExpectCommit()
   118  	mock.ExpectBegin()
   119  	mock.ExpectExec(clearCheckPointSQL).WithArgs(cpid).WillReturnResult(sqlmock.NewResult(0, 1))
   120  	mock.ExpectCommit()
   121  
   122  	dbConn, err := db.Conn(tcontext.Background().Context())
   123  	c.Assert(err, IsNil)
   124  	conn := dbconn.NewDBConn(s.cfg, conn.NewBaseConnForTest(dbConn, &retry.FiniteRetryStrategy{}))
   125  	cp.(*RemoteCheckPoint).dbConn = conn
   126  	err = cp.(*RemoteCheckPoint).prepare(tctx)
   127  	c.Assert(err, IsNil)
   128  	c.Assert(cp.Clear(tctx), IsNil)
   129  
   130  	// test operation for global checkpoint
   131  	s.testGlobalCheckPoint(c, cp)
   132  
   133  	// test operation for table checkpoint
   134  	s.testTableCheckPoint(c, cp)
   135  }
   136  
   137  func (s *testCheckpointSuite) testGlobalCheckPoint(c *C, cp CheckPoint) {
   138  	tctx := tcontext.Background()
   139  
   140  	// global checkpoint init to min
   141  	c.Assert(cp.GlobalPoint().Position, Equals, binlog.MinPosition)
   142  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, binlog.MinPosition)
   143  
   144  	// try load, but should load nothing
   145  	s.mock.ExpectQuery(loadCheckPointSQL).WillReturnRows(sqlmock.NewRows(nil))
   146  	err := cp.Load(tctx)
   147  	c.Assert(err, IsNil)
   148  	c.Assert(cp.GlobalPoint().Position, Equals, binlog.MinPosition)
   149  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, binlog.MinPosition)
   150  
   151  	oldMode := s.cfg.Mode
   152  	oldDir := s.cfg.Dir
   153  	defer func() {
   154  		s.cfg.Mode = oldMode
   155  		s.cfg.Dir = oldDir
   156  	}()
   157  
   158  	pos1 := mysql.Position{
   159  		Name: "mysql-bin.000003",
   160  		Pos:  1943,
   161  	}
   162  
   163  	s.mock.ExpectQuery(loadCheckPointSQL).WithArgs(cpid).WillReturnRows(sqlmock.NewRows(nil))
   164  	err = cp.Load(tctx)
   165  	c.Assert(err, IsNil)
   166  	cp.SaveGlobalPoint(binlog.Location{Position: pos1})
   167  
   168  	s.mock.ExpectBegin()
   169  	s.mock.ExpectExec("(162)?"+flushCheckPointSQL).WithArgs(cpid, "", "", pos1.Name, pos1.Pos, "", "", 0, "", "null", true).WillReturnResult(sqlmock.NewResult(0, 1))
   170  	s.mock.ExpectCommit()
   171  	// Create a new snapshot, and discard it, then create a new snapshot again.
   172  	cp.Snapshot(true)
   173  	cp.DiscardPendingSnapshots()
   174  	snap := cp.Snapshot(true)
   175  	err = cp.FlushPointsExcept(tctx, snap.id, nil, nil, nil)
   176  	c.Assert(err, IsNil)
   177  	c.Assert(cp.GlobalPoint().Position, Equals, pos1)
   178  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   179  
   180  	// try load from config
   181  	pos1.Pos = 2044
   182  	s.cfg.Mode = config.ModeIncrement
   183  	s.cfg.Meta = &config.Meta{BinLogName: pos1.Name, BinLogPos: pos1.Pos}
   184  	err = cp.LoadMeta(tctx.Ctx)
   185  	c.Assert(err, IsNil)
   186  	c.Assert(cp.GlobalPoint().Position, Equals, pos1)
   187  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   188  
   189  	s.cfg.Mode = oldMode
   190  	s.cfg.Meta = nil
   191  
   192  	// test save global point
   193  	pos2 := mysql.Position{
   194  		Name: "mysql-bin.000005",
   195  		Pos:  2052,
   196  	}
   197  	cp.SaveGlobalPoint(binlog.Location{Position: pos2})
   198  	c.Assert(cp.GlobalPoint().Position, Equals, pos2)
   199  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   200  
   201  	// test rollback
   202  	cp.Rollback()
   203  	c.Assert(cp.GlobalPoint().Position, Equals, pos1)
   204  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   205  
   206  	// save again
   207  	cp.SaveGlobalPoint(binlog.Location{Position: pos2})
   208  	c.Assert(cp.GlobalPoint().Position, Equals, pos2)
   209  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   210  
   211  	// flush + rollback
   212  	s.mock.ExpectBegin()
   213  	s.mock.ExpectExec("(202)?"+flushCheckPointSQL).WithArgs(cpid, "", "", pos2.Name, pos2.Pos, "", "", 0, "", "null", true).WillReturnResult(sqlmock.NewResult(0, 1))
   214  	s.mock.ExpectCommit()
   215  	err = cp.FlushPointsExcept(tctx, cp.Snapshot(true).id, nil, nil, nil)
   216  	c.Assert(err, IsNil)
   217  	cp.Rollback()
   218  	c.Assert(cp.GlobalPoint().Position, Equals, pos2)
   219  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos2)
   220  
   221  	// try load from DB
   222  	pos3 := pos2
   223  	pos3.Pos = pos2.Pos + 1000 // > pos2 to enable save
   224  	cp.SaveGlobalPoint(binlog.Location{Position: pos3})
   225  	columns := []string{"cp_schema", "cp_table", "binlog_name", "binlog_pos", "binlog_gtid", "exit_safe_binlog_name", "exit_safe_binlog_pos", "exit_safe_binlog_gtid", "table_info", "is_global"}
   226  	s.mock.ExpectQuery(loadCheckPointSQL).WithArgs(cpid).WillReturnRows(sqlmock.NewRows(columns).AddRow("", "", pos2.Name, pos2.Pos, "", "", 0, "", "null", true))
   227  	err = cp.Load(tctx)
   228  	c.Assert(err, IsNil)
   229  	c.Assert(cp.GlobalPoint().Position, Equals, pos2)
   230  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos2)
   231  
   232  	// test save older point
   233  	/*var buf bytes.Buffer
   234  	log.SetOutput(&buf)
   235  	cp.SaveGlobalPoint(pos1)
   236  	c.Assert(cp.GlobalPoint(), Equals, pos2)
   237  	c.Assert(cp.FlushedGlobalPoint(), Equals, pos2)
   238  	matchStr := fmt.Sprintf(".*try to save %s is older than current pos %s", pos1, pos2)
   239  	matchStr = strings.Replace(strings.Replace(matchStr, ")", "\\)", -1), "(", "\\(", -1)
   240  	c.Assert(strings.TrimSpace(buf.String()), Matches, matchStr)
   241  	log.SetOutput(os.Stdout)*/
   242  
   243  	// test clear
   244  	s.mock.ExpectBegin()
   245  	s.mock.ExpectExec(clearCheckPointSQL).WithArgs(cpid).WillReturnResult(sqlmock.NewResult(0, 1))
   246  	s.mock.ExpectCommit()
   247  	err = cp.Clear(tctx)
   248  	c.Assert(err, IsNil)
   249  	c.Assert(cp.GlobalPoint().Position, Equals, binlog.MinPosition)
   250  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, binlog.MinPosition)
   251  
   252  	s.mock.ExpectQuery(loadCheckPointSQL).WillReturnRows(sqlmock.NewRows(nil))
   253  	err = cp.Load(tctx)
   254  	c.Assert(err, IsNil)
   255  	c.Assert(cp.GlobalPoint().Position, Equals, binlog.MinPosition)
   256  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, binlog.MinPosition)
   257  
   258  	// try load from mydumper's output
   259  	dir := c.MkDir()
   260  
   261  	filename := filepath.Join(dir, "metadata")
   262  	err = os.WriteFile(filename, []byte(
   263  		fmt.Sprintf("SHOW MASTER STATUS:\n\tLog: %s\n\tPos: %d\n\tGTID:\n\nSHOW SLAVE STATUS:\n\tHost: %s\n\tLog: %s\n\tPos: %d\n\tGTID:\n\n", pos1.Name, pos1.Pos, "slave_host", pos1.Name, pos1.Pos+1000)),
   264  		0o644)
   265  	c.Assert(err, IsNil)
   266  	s.cfg.Mode = config.ModeAll
   267  	s.cfg.Dir = dir
   268  	c.Assert(cp.LoadMeta(tctx.Ctx), IsNil)
   269  
   270  	// should flush because checkpoint hasn't been updated before (cp.globalPointCheckOrSaveTime.IsZero() == true).
   271  	snapshot := cp.Snapshot(true)
   272  	c.Assert(snapshot.id, Equals, 4)
   273  
   274  	s.mock.ExpectQuery(loadCheckPointSQL).WillReturnRows(sqlmock.NewRows(nil))
   275  	err = cp.Load(tctx)
   276  	c.Assert(err, IsNil)
   277  	c.Assert(cp.GlobalPoint().Position, Equals, pos1)
   278  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   279  
   280  	s.mock.ExpectBegin()
   281  	s.mock.ExpectExec(clearCheckPointSQL).WithArgs(cpid).WillReturnResult(sqlmock.NewResult(0, 1))
   282  	s.mock.ExpectCommit()
   283  	err = cp.Clear(tctx)
   284  	c.Assert(err, IsNil)
   285  
   286  	// check dumpling write exitSafeModeLocation in metadata
   287  	err = os.WriteFile(filename, []byte(
   288  		fmt.Sprintf(`SHOW MASTER STATUS:
   289  	Log: %s
   290  	Pos: %d
   291  	GTID:
   292  
   293  SHOW SLAVE STATUS:
   294  	Host: %s
   295  	Log: %s
   296  	Pos: %d
   297  	GTID:
   298  
   299  SHOW MASTER STATUS: /* AFTER CONNECTION POOL ESTABLISHED */
   300  	Log: %s
   301  	Pos: %d
   302  	GTID:
   303  `, pos1.Name, pos1.Pos, "slave_host", pos1.Name, pos1.Pos+1000, pos2.Name, pos2.Pos)), 0o644)
   304  	c.Assert(err, IsNil)
   305  	c.Assert(cp.LoadMeta(tctx.Ctx), IsNil)
   306  
   307  	// should flush because exitSafeModeLocation is true
   308  	snapshot = cp.Snapshot(true)
   309  	c.Assert(snapshot, NotNil)
   310  	s.mock.ExpectBegin()
   311  	s.mock.ExpectExec("(202)?"+flushCheckPointSQL).WithArgs(cpid, "", "", pos1.Name, pos1.Pos, "", pos2.Name, pos2.Pos, "", "null", true).WillReturnResult(sqlmock.NewResult(0, 1))
   312  	s.mock.ExpectCommit()
   313  	err = cp.FlushPointsExcept(tctx, snapshot.id, nil, nil, nil)
   314  	c.Assert(err, IsNil)
   315  	s.mock.ExpectQuery(loadCheckPointSQL).WillReturnRows(sqlmock.NewRows(nil))
   316  	err = cp.Load(tctx)
   317  	c.Assert(err, IsNil)
   318  	c.Assert(cp.GlobalPoint().Position, Equals, pos1)
   319  	c.Assert(cp.FlushedGlobalPoint().Position, Equals, pos1)
   320  	c.Assert(cp.SafeModeExitPoint().Position, Equals, pos2)
   321  
   322  	// when use async flush, even exitSafeModeLocation is true we won't flush
   323  	c.Assert(cp.LoadMeta(tctx.Ctx), IsNil)
   324  	snapshot = cp.Snapshot(false)
   325  	c.Assert(snapshot, IsNil)
   326  }
   327  
   328  func (s *testCheckpointSuite) testTableCheckPoint(c *C, cp CheckPoint) {
   329  	var (
   330  		tctx  = tcontext.Background()
   331  		table = &filter.Table{
   332  			Schema: "test_db",
   333  			Name:   "test_table",
   334  		}
   335  		schemaName = "test_db"
   336  		tableName  = "test_table"
   337  		pos1       = mysql.Position{
   338  			Name: "mysql-bin.000008",
   339  			Pos:  123,
   340  		}
   341  		pos2 = mysql.Position{
   342  			Name: "mysql-bin.000008",
   343  			Pos:  456,
   344  		}
   345  		err error
   346  	)
   347  
   348  	// not exist
   349  	older := cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   350  	c.Assert(older, IsFalse)
   351  
   352  	// save
   353  	cp.SaveTablePoint(table, binlog.Location{Position: pos2}, nil)
   354  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   355  	c.Assert(older, IsTrue)
   356  
   357  	// rollback, to min
   358  	cp.Rollback()
   359  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   360  	c.Assert(older, IsFalse)
   361  
   362  	// save again
   363  	cp.SaveTablePoint(table, binlog.Location{Position: pos2}, nil)
   364  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   365  	c.Assert(older, IsTrue)
   366  
   367  	// flush + rollback
   368  	s.mock.ExpectBegin()
   369  	s.mock.ExpectExec("(284)?"+flushCheckPointSQL).WithArgs(cpid, table.Schema, table.Name, pos2.Name, pos2.Pos, "", "", 0, "", sqlmock.AnyArg(), false).WillReturnResult(sqlmock.NewResult(0, 1))
   370  	s.mock.ExpectCommit()
   371  	err = cp.FlushPointsExcept(tctx, cp.Snapshot(true).id, nil, nil, nil)
   372  	c.Assert(err, IsNil)
   373  	cp.Rollback()
   374  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   375  	c.Assert(older, IsTrue)
   376  
   377  	// save
   378  	cp.SaveTablePoint(table, binlog.Location{Position: pos2}, nil)
   379  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   380  	c.Assert(older, IsTrue)
   381  
   382  	// delete
   383  	s.mock.ExpectBegin()
   384  	s.mock.ExpectExec(deleteCheckPointSQL).WithArgs(cpid, schemaName, tableName).WillReturnResult(sqlmock.NewResult(0, 1))
   385  	s.mock.ExpectCommit()
   386  	c.Assert(cp.DeleteTablePoint(tctx, table), IsNil)
   387  	s.mock.ExpectBegin()
   388  	s.mock.ExpectExec(deleteSchemaPointSQL).WithArgs(cpid, schemaName).WillReturnResult(sqlmock.NewResult(0, 1))
   389  	s.mock.ExpectCommit()
   390  	c.Assert(cp.DeleteSchemaPoint(tctx, schemaName), IsNil)
   391  
   392  	ctx := context.Background()
   393  
   394  	// test save with table info and rollback
   395  	c.Assert(s.tracker.CreateSchemaIfNotExists(schemaName), IsNil)
   396  	stmt, err := parseSQL("create table " + tableName + " (c int);")
   397  	c.Assert(err, IsNil)
   398  	err = s.tracker.Exec(ctx, schemaName, stmt)
   399  	c.Assert(err, IsNil)
   400  	ti, err := s.tracker.GetTableInfo(table)
   401  	c.Assert(err, IsNil)
   402  	cp.SaveTablePoint(table, binlog.Location{Position: pos1}, ti)
   403  	rcp := cp.(*RemoteCheckPoint)
   404  	c.Assert(rcp.points[schemaName][tableName].TableInfo(), NotNil)
   405  	c.Assert(rcp.points[schemaName][tableName].flushedPoint.ti, IsNil)
   406  
   407  	cp.Rollback()
   408  	rcp = cp.(*RemoteCheckPoint)
   409  	c.Assert(rcp.points[schemaName][tableName].TableInfo(), IsNil)
   410  	c.Assert(rcp.points[schemaName][tableName].flushedPoint.ti, IsNil)
   411  
   412  	// test save, flush and rollback to not nil table info
   413  	cp.SaveTablePoint(table, binlog.Location{Position: pos1}, ti)
   414  	tiBytes, _ := json.Marshal(ti)
   415  	s.mock.ExpectBegin()
   416  	s.mock.ExpectExec(flushCheckPointSQL).WithArgs(cpid, schemaName, tableName, pos1.Name, pos1.Pos, "", "", 0, "", string(tiBytes), false).WillReturnResult(sqlmock.NewResult(0, 1))
   417  	s.mock.ExpectCommit()
   418  	lastGlobalPoint := cp.GlobalPoint()
   419  	lastGlobalPointSavedTime := cp.GlobalPointSaveTime()
   420  	c.Assert(cp.FlushPointsExcept(tctx, cp.Snapshot(true).id, nil, nil, nil), IsNil)
   421  	c.Assert(cp.GlobalPoint(), Equals, lastGlobalPoint)
   422  	c.Assert(cp.GlobalPointSaveTime(), Equals, lastGlobalPointSavedTime)
   423  	stmt, err = parseSQL("alter table " + tableName + " add c2 int;")
   424  	c.Assert(err, IsNil)
   425  	err = s.tracker.Exec(ctx, schemaName, stmt)
   426  	c.Assert(err, IsNil)
   427  	ti2, err := s.tracker.GetTableInfo(table)
   428  	c.Assert(err, IsNil)
   429  	cp.SaveTablePoint(table, binlog.Location{Position: pos2}, ti2)
   430  	cp.Rollback()
   431  
   432  	// clear, to min
   433  	s.mock.ExpectBegin()
   434  	s.mock.ExpectExec(clearCheckPointSQL).WithArgs(cpid).WillReturnResult(sqlmock.NewResult(0, 1))
   435  	s.mock.ExpectCommit()
   436  	err = cp.Clear(tctx)
   437  	c.Assert(err, IsNil)
   438  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   439  	c.Assert(older, IsFalse)
   440  
   441  	// test save table point less than global point
   442  	func() {
   443  		defer func() {
   444  			r := recover()
   445  			matchStr := ".*less than global checkpoint.*"
   446  			c.Assert(r, Matches, matchStr)
   447  		}()
   448  		cp.SaveGlobalPoint(binlog.Location{Position: pos2})
   449  		cp.SaveTablePoint(table, binlog.Location{Position: pos1}, nil)
   450  	}()
   451  
   452  	// flush but except + rollback
   453  	s.mock.ExpectBegin()
   454  	s.mock.ExpectExec("(320)?"+flushCheckPointSQL).WithArgs(cpid, "", "", pos2.Name, pos2.Pos, "", "", 0, "", "null", true).WillReturnResult(sqlmock.NewResult(0, 1))
   455  	s.mock.ExpectCommit()
   456  	lastGlobalPoint = cp.GlobalPoint()
   457  	lastGlobalPointSavedTime = cp.GlobalPointSaveTime()
   458  	err = cp.FlushPointsExcept(tctx, cp.Snapshot(true).id, []*filter.Table{table}, nil, nil)
   459  	fmt.Println(cp.GlobalPoint(), lastGlobalPoint)
   460  	c.Assert(cp.GlobalPoint(), Equals, lastGlobalPoint)
   461  	c.Assert(cp.GlobalPointSaveTime(), Not(Equals), lastGlobalPointSavedTime)
   462  	c.Assert(err, IsNil)
   463  	cp.Rollback()
   464  	older = cp.IsOlderThanTablePoint(table, binlog.Location{Position: pos1})
   465  	c.Assert(older, IsFalse)
   466  
   467  	s.mock.ExpectBegin()
   468  	s.mock.ExpectExec(clearCheckPointSQL).WithArgs(cpid).WillReturnResult(sqlmock.NewResult(0, 1))
   469  	s.mock.ExpectCommit()
   470  	c.Assert(cp.Clear(tctx), IsNil)
   471  	// load table point and exitSafe, with enable GTID
   472  	s.cfg.EnableGTID = true
   473  	flavor := mysql.MySQLFlavor
   474  	gSetStr := "03fc0263-28c7-11e7-a653-6c0b84d59f30:123"
   475  	gs, _ := gtid.ParserGTID(flavor, gSetStr)
   476  	columns := []string{"cp_schema", "cp_table", "binlog_name", "binlog_pos", "binlog_gtid", "exit_safe_binlog_name", "exit_safe_binlog_pos", "exit_safe_binlog_gtid", "table_info", "is_global"}
   477  	s.mock.ExpectQuery(loadCheckPointSQL).WithArgs(cpid).WillReturnRows(
   478  		sqlmock.NewRows(columns).AddRow("", "", pos2.Name, pos2.Pos, gs.String(), pos2.Name, pos2.Pos, gs.String(), "null", true).
   479  			AddRow(schemaName, tableName, pos2.Name, pos2.Pos, gs.String(), "", 0, "", tiBytes, false))
   480  	err = cp.Load(tctx)
   481  	c.Assert(err, IsNil)
   482  	c.Assert(cp.GlobalPoint(), DeepEquals, binlog.NewLocation(pos2, gs))
   483  	rcp = cp.(*RemoteCheckPoint)
   484  	c.Assert(rcp.points[schemaName][tableName].TableInfo(), NotNil)
   485  	c.Assert(rcp.points[schemaName][tableName].flushedPoint.ti, NotNil)
   486  	c.Assert(*rcp.safeModeExitPoint, DeepEquals, binlog.NewLocation(pos2, gs))
   487  }
   488  
   489  func TestRemoteCheckPointLoadIntoSchemaTracker(t *testing.T) {
   490  	cfg := genDefaultSubTaskConfig4Test()
   491  	cfg.WorkerCount = 0
   492  	ctx := context.Background()
   493  
   494  	db, _, err := sqlmock.New()
   495  	require.NoError(t, err)
   496  	dbConn, err := db.Conn(ctx)
   497  	require.NoError(t, err)
   498  	downstreamTrackConn := dbconn.NewDBConn(cfg, conn.NewBaseConnForTest(dbConn, &retry.FiniteRetryStrategy{}))
   499  	schemaTracker, err := schema.NewTestTracker(ctx, cfg.Name, downstreamTrackConn, dlog.L())
   500  	require.NoError(t, err)
   501  	defer schemaTracker.Close() //nolint
   502  
   503  	tbl1 := &filter.Table{Schema: "test", Name: "tbl1"}
   504  	tbl2 := &filter.Table{Schema: "test", Name: "tbl2"}
   505  
   506  	// before load
   507  	_, err = schemaTracker.GetTableInfo(tbl1)
   508  	require.Error(t, err)
   509  	_, err = schemaTracker.GetTableInfo(tbl2)
   510  	require.Error(t, err)
   511  
   512  	cp := NewRemoteCheckPoint(tcontext.Background(), cfg, nil, "1")
   513  	checkpoint := cp.(*RemoteCheckPoint)
   514  
   515  	parser, err := conn.GetParserFromSQLModeStr("")
   516  	require.NoError(t, err)
   517  	createNode, err := parser.ParseOneStmt("create table tbl1(id int)", "", "")
   518  	require.NoError(t, err)
   519  	ti, err := tidbddl.BuildTableInfoFromAST(createNode.(*ast.CreateTableStmt))
   520  	require.NoError(t, err)
   521  
   522  	tp1 := tablePoint{ti: ti}
   523  	tp2 := tablePoint{}
   524  	checkpoint.points[tbl1.Schema] = make(map[string]*binlogPoint)
   525  	checkpoint.points[tbl1.Schema][tbl1.Name] = &binlogPoint{flushedPoint: tp1}
   526  	checkpoint.points[tbl2.Schema][tbl2.Name] = &binlogPoint{flushedPoint: tp2}
   527  
   528  	// after load
   529  	err = checkpoint.LoadIntoSchemaTracker(ctx, schemaTracker)
   530  	require.NoError(t, err)
   531  	tableInfo, err := schemaTracker.GetTableInfo(tbl1)
   532  	require.NoError(t, err)
   533  	require.Len(t, tableInfo.Columns, 1)
   534  	_, err = schemaTracker.GetTableInfo(tbl2)
   535  	require.Error(t, err)
   536  
   537  	// test BatchCreateTableWithInfo will not meet kv entry too large error
   538  
   539  	// create 100K comment string
   540  	comment := make([]byte, 0, 100000)
   541  	for i := 0; i < 100000; i++ {
   542  		comment = append(comment, 'A')
   543  	}
   544  	ti.Comment = string(comment)
   545  
   546  	tp1 = tablePoint{ti: ti}
   547  	amount := 100
   548  	for i := 0; i < amount; i++ {
   549  		tableName := fmt.Sprintf("tbl_%d", i)
   550  		checkpoint.points[tbl1.Schema][tableName] = &binlogPoint{flushedPoint: tp1}
   551  	}
   552  	err = checkpoint.LoadIntoSchemaTracker(ctx, schemaTracker)
   553  	require.NoError(t, err)
   554  }
   555  
   556  func TestLastFlushOutdated(t *testing.T) {
   557  	cfg := genDefaultSubTaskConfig4Test()
   558  	cfg.WorkerCount = 0
   559  	cfg.CheckpointFlushInterval = 1
   560  
   561  	cp := NewRemoteCheckPoint(tcontext.Background(), cfg, nil, "1")
   562  	checkpoint := cp.(*RemoteCheckPoint)
   563  	checkpoint.globalPointSaveTime = time.Now().Add(-2 * time.Second)
   564  
   565  	require.True(t, checkpoint.LastFlushOutdated())
   566  	require.Nil(t, checkpoint.Snapshot(true))
   567  	// though snapshot is nil, checkpoint is not outdated
   568  	require.False(t, checkpoint.LastFlushOutdated())
   569  }