github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/checker/check_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 checker
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"testing"
    20  
    21  	"github.com/DATA-DOG/go-sqlmock"
    22  	gmysql "github.com/go-sql-driver/mysql"
    23  	"github.com/pingcap/tidb/pkg/parser/mysql"
    24  	router "github.com/pingcap/tidb/pkg/util/table-router"
    25  	"github.com/pingcap/tiflow/dm/config"
    26  	"github.com/pingcap/tiflow/dm/ctl/common"
    27  	"github.com/pingcap/tiflow/dm/pkg/conn"
    28  	"github.com/pingcap/tiflow/dm/pkg/cputil"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  var (
    33  	schema     = "db_1"
    34  	tb1        = "t_1"
    35  	tb2        = "t_2"
    36  	metaSchema = "dm_meta"
    37  	taskName   = "test"
    38  )
    39  
    40  func ignoreExcept(itemMap map[string]struct{}) []string {
    41  	items := []string{
    42  		config.DumpPrivilegeChecking,
    43  		config.ReplicationPrivilegeChecking,
    44  		config.VersionChecking,
    45  		config.ServerIDChecking,
    46  		config.BinlogEnableChecking,
    47  		config.BinlogFormatChecking,
    48  		config.BinlogRowImageChecking,
    49  		config.TableSchemaChecking,
    50  		config.ShardTableSchemaChecking,
    51  		config.ShardAutoIncrementIDChecking,
    52  		config.OnlineDDLChecking,
    53  		config.BinlogDBChecking,
    54  		config.MetaPositionChecking,
    55  		config.ConnNumberChecking,
    56  		config.TargetDBPrivilegeChecking,
    57  		config.LightningEmptyRegionChecking,
    58  		config.LightningRegionDistributionChecking,
    59  		config.LightningDownstreamVersionChecking,
    60  		config.LightningFreeSpaceChecking,
    61  		config.LightningMutexFeatureChecking,
    62  		config.LightningTableEmptyChecking,
    63  	}
    64  	ignoreCheckingItems := make([]string, 0, len(items)-len(itemMap))
    65  	for _, i := range items {
    66  		if _, ok := itemMap[i]; !ok {
    67  			ignoreCheckingItems = append(ignoreCheckingItems, i)
    68  		}
    69  	}
    70  	return ignoreCheckingItems
    71  }
    72  
    73  func TestIgnoreAllCheckingItems(t *testing.T) {
    74  	msg, err := CheckSyncConfig(context.Background(), nil, common.DefaultErrorCnt, common.DefaultWarnCnt)
    75  	require.NoError(t, err)
    76  	require.Len(t, msg, 0)
    77  
    78  	result, err := RunCheckOnConfigs(context.Background(), nil, false, 100, 100)
    79  	require.NoError(t, err)
    80  	require.Nil(t, result)
    81  
    82  	cfgs := []*config.SubTaskConfig{
    83  		{
    84  			Mode:                config.ModeAll,
    85  			IgnoreCheckingItems: []string{config.AllChecking},
    86  		},
    87  	}
    88  	msg, err = CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
    89  	require.NoError(t, err)
    90  	require.Len(t, msg, 0)
    91  
    92  	result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
    93  	require.NoError(t, err)
    94  	require.Nil(t, result)
    95  }
    96  
    97  func TestDumpPrivilegeChecking(t *testing.T) {
    98  	cfgs := []*config.SubTaskConfig{
    99  		{
   100  			Mode:                config.ModeAll,
   101  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.DumpPrivilegeChecking: {}}),
   102  		},
   103  	}
   104  
   105  	// test not enough privileges
   106  
   107  	mock := initMockDB(t)
   108  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   109  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   110  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   111  	require.Regexp(t, "(.|\n)*lack.*Select(.|\n)*", err.Error())
   112  	require.Len(t, msg, 0)
   113  
   114  	mock = initMockDB(t)
   115  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   116  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   117  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   118  	require.NoError(t, err)
   119  	require.Equal(t, int64(1), result.Summary.Failed)
   120  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Select privilege")
   121  
   122  	// test dumpWholeInstance
   123  
   124  	mock = initMockDB(t)
   125  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   126  		AddRow("GRANT SELECT ON db.* TO 'haha'@'%'"))
   127  	result, err = RunCheckOnConfigs(context.Background(), cfgs, true, 100, 100)
   128  	require.NoError(t, err)
   129  	require.Equal(t, int64(1), result.Summary.Failed)
   130  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Select global (*.*) privilege")
   131  
   132  	// happy path
   133  
   134  	checkHappyPath(t, func() {
   135  		mock := initMockDB(t)
   136  		mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   137  			AddRow("GRANT RELOAD,SELECT ON *.* TO 'haha'@'%'"))
   138  	}, cfgs)
   139  }
   140  
   141  func TestReplicationPrivilegeChecking(t *testing.T) {
   142  	cfgs := []*config.SubTaskConfig{
   143  		{
   144  			Mode:                config.ModeAll,
   145  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ReplicationPrivilegeChecking: {}}),
   146  		},
   147  	}
   148  
   149  	// test not enough privileges
   150  
   151  	mock := initMockDB(t)
   152  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   153  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   154  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   155  	require.Regexp(t, "(.|\n)*lack.*REPLICATION SLAVE(.|\n)*", err.Error())
   156  	require.Regexp(t, "(.|\n)*lack.*REPLICATION CLIENT(.|\n)*", err.Error())
   157  	require.Len(t, msg, 0)
   158  
   159  	mock = initMockDB(t)
   160  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   161  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   162  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   163  	require.NoError(t, err)
   164  	require.Equal(t, int64(1), result.Summary.Failed)
   165  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of REPLICATION SLAVE global (*.*) privilege")
   166  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of REPLICATION CLIENT global (*.*) privilege")
   167  
   168  	// happy path
   169  
   170  	checkHappyPath(t, func() {
   171  		mock := initMockDB(t)
   172  		mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   173  			AddRow("GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'haha'@'%'"))
   174  	}, cfgs)
   175  }
   176  
   177  func TestRunCheckOnConfigsLimit(t *testing.T) {
   178  	cfgs := []*config.SubTaskConfig{
   179  		{
   180  			Mode: config.ModeAll,
   181  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{
   182  				config.DumpPrivilegeChecking:        {},
   183  				config.ReplicationPrivilegeChecking: {},
   184  			}),
   185  		},
   186  	}
   187  
   188  	// with limit = 100, there should be 2 error
   189  
   190  	mock := initMockDB(t)
   191  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   192  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   193  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   194  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   195  
   196  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   197  	require.NoError(t, err)
   198  	require.EqualValues(t, 2, result.Summary.Failed)
   199  	require.Len(t, result.Results, 2)
   200  
   201  	// test limit = 1
   202  
   203  	mock = initMockDB(t)
   204  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   205  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   206  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   207  		AddRow("GRANT USAGE ON *.* TO 'haha'@'%'"))
   208  
   209  	result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 0, 1)
   210  	require.NoError(t, err)
   211  	require.EqualValues(t, 2, result.Summary.Failed)
   212  	require.Len(t, result.Results, 1)
   213  
   214  	// test limit warning of sub-results
   215  
   216  	cfgs = []*config.SubTaskConfig{
   217  		{
   218  			Mode:                config.ModeAll,
   219  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}),
   220  		},
   221  	}
   222  	errNoSuchTable := &gmysql.MySQLError{Number: 1146, Message: "Table 'xxx' doesn't exist"}
   223  
   224  	createTable1 := `CREATE TABLE %s (
   225  				  id int(11) DEFAULT NULL,
   226  				  b int(11) DEFAULT NULL
   227  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   228  
   229  	mock = initMockDB(t)
   230  	mock.MatchExpectationsInOrder(false)
   231  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   232  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   233  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   234  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   235  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock
   236  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   237  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   238  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock
   239  
   240  	result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   241  	require.NoError(t, err)
   242  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist")
   243  	require.Contains(t, result.Results[0].Errors[1].ShortErr, "primary/unique key does not exist")
   244  	require.NoError(t, mock.ExpectationsWereMet())
   245  
   246  	mock = initMockDB(t)
   247  	mock.MatchExpectationsInOrder(false)
   248  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   249  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   250  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   251  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   252  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock
   253  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   254  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   255  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock
   256  
   257  	result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 1, 0)
   258  	require.NoError(t, err)
   259  	require.Len(t, result.Results[0].Errors, 1)
   260  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist")
   261  	require.NoError(t, mock.ExpectationsWereMet())
   262  }
   263  
   264  func TestTargetDBPrivilegeChecking(t *testing.T) {
   265  	cfgs := []*config.SubTaskConfig{
   266  		{
   267  			Mode:                config.ModeAll,
   268  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TargetDBPrivilegeChecking: {}}),
   269  		},
   270  	}
   271  
   272  	// test not enough privileges
   273  
   274  	mock := initMockDB(t)
   275  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   276  		AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER ON *.* TO 'test'@'%'"))
   277  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   278  	require.NoError(t, err)
   279  	require.Contains(t, msg, "lack of Drop global (*.*) privilege; lack of Index global (*.*) privilege; ")
   280  
   281  	mock = initMockDB(t)
   282  	mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   283  		AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER ON *.* TO 'test'@'%'"))
   284  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   285  	require.NoError(t, err)
   286  	require.Equal(t, int64(1), result.Summary.Warning)
   287  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Drop global (*.*) privilege")
   288  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "lack of Index global (*.*) privilege")
   289  
   290  	// happy path
   291  
   292  	checkHappyPath(t, func() {
   293  		mock := initMockDB(t)
   294  		mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   295  			AddRow("GRANT SELECT,UPDATE,CREATE,DELETE,INSERT,ALTER,INDEX,DROP ON *.* TO 'test'@'%'"))
   296  	}, cfgs)
   297  
   298  	checkHappyPath(t, func() {
   299  		mock := initMockDB(t)
   300  		mock.ExpectQuery("SHOW GRANTS").WillReturnRows(sqlmock.NewRows([]string{"Grants for User"}).
   301  			AddRow("GRANT ALL PRIVILEGES ON *.* TO 'test'@'%'"))
   302  	}, cfgs)
   303  }
   304  
   305  func TestVersionChecking(t *testing.T) {
   306  	cfgs := []*config.SubTaskConfig{
   307  		{
   308  			Mode:                config.ModeAll,
   309  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.VersionChecking: {}}),
   310  		},
   311  	}
   312  
   313  	// happy path
   314  
   315  	checkHappyPath(t, func() {
   316  		mock := initMockDB(t)
   317  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   318  			AddRow("version", "5.7.26-log"))
   319  	}, cfgs)
   320  
   321  	// MariaDB should have a warning
   322  
   323  	mock := initMockDB(t)
   324  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   325  		AddRow("version", "10.1.29-MariaDB"))
   326  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   327  	require.NoError(t, err)
   328  	require.Contains(t, msg, "Migrating from MariaDB is still experimental")
   329  
   330  	mock = initMockDB(t)
   331  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   332  		AddRow("version", "10.1.29-MariaDB"))
   333  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   334  	require.NoError(t, err)
   335  	require.True(t, result.Summary.Passed)
   336  	require.Equal(t, int64(1), result.Summary.Warning)
   337  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "Migrating from MariaDB is still experimental.")
   338  
   339  	// too low MySQL version
   340  
   341  	mock = initMockDB(t)
   342  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   343  		AddRow("version", "5.5.26-log"))
   344  	msg, err = CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   345  	require.NoError(t, err)
   346  	require.Regexp(t, "(.|\n)*version suggested at least .* but got 5.5.26(.|\n)*", msg)
   347  
   348  	mock = initMockDB(t)
   349  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   350  		AddRow("version", "5.5.26-log"))
   351  	result, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   352  	require.NoError(t, err)
   353  	require.True(t, result.Summary.Passed)
   354  	require.Equal(t, int64(1), result.Summary.Warning)
   355  	require.Regexp(t, "version suggested at least .* but got 5.5.26", result.Results[0].Errors[0].ShortErr)
   356  }
   357  
   358  func TestServerIDChecking(t *testing.T) {
   359  	cfgs := []*config.SubTaskConfig{
   360  		{
   361  			Mode:                config.ModeAll,
   362  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ServerIDChecking: {}}),
   363  		},
   364  	}
   365  
   366  	// not explicit server ID
   367  
   368  	mock := initMockDB(t)
   369  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   370  		AddRow("server_id", "0"))
   371  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   372  	require.NoError(t, err)
   373  	require.Contains(t, msg, "Set server_id greater than 0")
   374  
   375  	mock = initMockDB(t)
   376  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   377  		AddRow("server_id", "0"))
   378  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   379  	require.NoError(t, err)
   380  	require.True(t, result.Summary.Passed)
   381  	require.Contains(t, result.Results[0].Instruction, "Set server_id greater than 0")
   382  
   383  	// happy path
   384  
   385  	checkHappyPath(t, func() {
   386  		mock := initMockDB(t)
   387  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'server_id'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   388  			AddRow("server_id", "1"))
   389  	}, cfgs)
   390  }
   391  
   392  func TestBinlogEnableChecking(t *testing.T) {
   393  	cfgs := []*config.SubTaskConfig{
   394  		{
   395  			Mode:                config.ModeAll,
   396  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogEnableChecking: {}}),
   397  		},
   398  	}
   399  
   400  	// forget to turn on binlog
   401  
   402  	mock := initMockDB(t)
   403  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   404  		AddRow("log_bin", "OFF"))
   405  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   406  	require.ErrorContains(t, err, "log_bin is OFF, and should be ON")
   407  	require.Len(t, msg, 0)
   408  
   409  	mock = initMockDB(t)
   410  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   411  		AddRow("log_bin", "OFF"))
   412  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   413  	require.NoError(t, err)
   414  	require.False(t, result.Summary.Passed)
   415  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "log_bin is OFF, and should be ON")
   416  
   417  	// happy path
   418  
   419  	checkHappyPath(t, func() {
   420  		mock := initMockDB(t)
   421  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'log_bin'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   422  			AddRow("log_bin", "ON"))
   423  	}, cfgs)
   424  }
   425  
   426  func TestBinlogFormatChecking(t *testing.T) {
   427  	cfgs := []*config.SubTaskConfig{
   428  		{
   429  			Mode:                config.ModeAll,
   430  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogFormatChecking: {}}),
   431  		},
   432  	}
   433  
   434  	// binlog_format is not correct
   435  
   436  	mock := initMockDB(t)
   437  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   438  		AddRow("binlog_format", "STATEMENT"))
   439  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   440  	require.ErrorContains(t, err, "binlog_format is STATEMENT, and should be ROW")
   441  	require.Len(t, msg, 0)
   442  
   443  	mock = initMockDB(t)
   444  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   445  		AddRow("binlog_format", "STATEMENT"))
   446  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   447  	require.NoError(t, err)
   448  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "binlog_format is STATEMENT, and should be ROW")
   449  
   450  	// happy path
   451  
   452  	checkHappyPath(t, func() {
   453  		mock := initMockDB(t)
   454  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_format'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   455  			AddRow("binlog_format", "ROW"))
   456  	}, cfgs)
   457  }
   458  
   459  func TestBinlogRowImageChecking(t *testing.T) {
   460  	cfgs := []*config.SubTaskConfig{
   461  		{
   462  			Mode:                config.ModeAll,
   463  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.BinlogRowImageChecking: {}}),
   464  		},
   465  	}
   466  
   467  	// binlog_row_image is not correct
   468  
   469  	mock := initMockDB(t)
   470  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   471  		AddRow("version", "5.7.26-log"))
   472  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   473  		AddRow("binlog_row_image", "MINIMAL"))
   474  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   475  	require.ErrorContains(t, err, "binlog_row_image is MINIMAL, and should be FULL")
   476  	require.Len(t, msg, 0)
   477  
   478  	mock = initMockDB(t)
   479  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   480  		AddRow("version", "5.7.26-log"))
   481  	mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   482  		AddRow("binlog_row_image", "MINIMAL"))
   483  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   484  	require.NoError(t, err)
   485  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "binlog_row_image is MINIMAL, and should be FULL")
   486  
   487  	// happy path
   488  
   489  	checkHappyPath(t, func() {
   490  		mock := initMockDB(t)
   491  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'version'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   492  			AddRow("version", "10.1.29-MariaDB"))
   493  		mock.ExpectQuery("SHOW GLOBAL VARIABLES LIKE 'binlog_row_image'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
   494  			AddRow("binlog_row_image", "FULL"))
   495  	}, cfgs)
   496  }
   497  
   498  func TestTableSchemaChecking(t *testing.T) {
   499  	cfgs := []*config.SubTaskConfig{
   500  		{
   501  			Mode:                config.ModeAll,
   502  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}),
   503  		},
   504  	}
   505  	errNoSuchTable := &gmysql.MySQLError{Number: 1146, Message: "Table 'xxx' doesn't exist"}
   506  
   507  	createTable1 := `CREATE TABLE %s (
   508  				  id int(11) DEFAULT NULL,
   509  				  b int(11) DEFAULT NULL
   510  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   511  	createTable2 := `CREATE TABLE %s (
   512  				  id int(11) DEFAULT NULL,
   513  				  b int(11) DEFAULT NULL,
   514  				  UNIQUE KEY id (id)
   515  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   516  
   517  	// no PK/UK should raise error per table
   518  
   519  	mock := initMockDB(t)
   520  	mock.MatchExpectationsInOrder(false)
   521  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   522  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   523  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   524  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   525  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock
   526  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   527  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   528  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock
   529  
   530  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   531  	require.NoError(t, err)
   532  	require.Contains(t, msg, "primary/unique key does not exist")
   533  	require.NoError(t, mock.ExpectationsWereMet())
   534  
   535  	mock = initMockDB(t)
   536  	mock.MatchExpectationsInOrder(false)
   537  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   538  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   539  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   540  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   541  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock
   542  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   543  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   544  	mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock
   545  
   546  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   547  	require.NoError(t, err)
   548  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "primary/unique key does not exist")
   549  	require.Contains(t, result.Results[0].Errors[1].ShortErr, "primary/unique key does not exist")
   550  	require.NoError(t, mock.ExpectationsWereMet())
   551  
   552  	// happy path
   553  
   554  	checkHappyPath(t, func() {
   555  		mock := initMockDB(t)
   556  		mock.MatchExpectationsInOrder(false)
   557  		mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   558  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   559  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   560  		mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1)))
   561  		mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_1`").WillReturnError(errNoSuchTable) // downstream connection mock
   562  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   563  		mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2)))
   564  		mock.ExpectQuery("SHOW CREATE TABLE `db_1`.`t_2`").WillReturnError(errNoSuchTable) // downstream connection mock
   565  	}, cfgs)
   566  }
   567  
   568  func TestShardTableSchemaChecking(t *testing.T) {
   569  	cfgs := []*config.SubTaskConfig{
   570  		{
   571  			MetaSchema: metaSchema,
   572  			Name:       taskName,
   573  			ShardMode:  config.ShardPessimistic,
   574  			RouteRules: []*router.TableRule{
   575  				{
   576  					SchemaPattern: schema,
   577  					TargetSchema:  "db",
   578  					TablePattern:  "t_*",
   579  					TargetTable:   "t",
   580  				},
   581  			},
   582  			Mode:                config.ModeAll,
   583  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ShardTableSchemaChecking: {}}),
   584  		},
   585  	}
   586  
   587  	createTable1 := `CREATE TABLE %s (
   588  				  id int(11) DEFAULT NULL,
   589  				  b int(11) DEFAULT NULL
   590  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   591  	createTable2 := `CREATE TABLE %s (
   592    					id int(11) DEFAULT NULL,
   593    					c int(11) DEFAULT NULL
   594  					) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   595  	errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable}
   596  	createTableSQL := "SHOW CREATE TABLE `%s`.`%s`"
   597  
   598  	// test different column definition
   599  
   600  	mock := initMockDB(t)
   601  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   602  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   603  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   604  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   605  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   606  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   607  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   608  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   609  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   610  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2)))
   611  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   612  	require.ErrorContains(t, err, "different column definition")
   613  	require.Len(t, msg, 0)
   614  
   615  	mock = initMockDB(t)
   616  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   617  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   618  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   619  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   620  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   621  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   622  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   623  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   624  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   625  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2)))
   626  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   627  	require.NoError(t, err)
   628  	require.Contains(t, result.Results[0].Errors[0].ShortErr, "different column definition")
   629  
   630  	// test success check
   631  
   632  	checkHappyPath(t, func() {
   633  		mock := initMockDB(t)
   634  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   635  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   636  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnError(errNoSuchTable)
   637  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   638  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   639  		mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   640  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   641  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   642  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   643  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   644  	}, cfgs)
   645  
   646  	// test existing checkpoint
   647  
   648  	checkHappyPath(t, func() {
   649  		mock := initMockDB(t)
   650  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LoaderCheckpoint(taskName))).WillReturnRows(sqlmock.
   651  			NewRows([]string{"Table", "Create Table"}).AddRow(cputil.LoaderCheckpoint(taskName), ""))
   652  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.LightningCheckpoint(taskName))).WillReturnRows(sqlmock.
   653  			NewRows([]string{"Table", "Create Table"}).AddRow(cputil.LightningCheckpoint(taskName), ""))
   654  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, metaSchema, cputil.SyncerCheckpoint(taskName))).WillReturnRows(sqlmock.
   655  			NewRows([]string{"Table", "Create Table"}).AddRow(cputil.SyncerCheckpoint(taskName), ""))
   656  	}, cfgs)
   657  }
   658  
   659  func TestShardAutoIncrementIDChecking(t *testing.T) {
   660  	cfgs := []*config.SubTaskConfig{
   661  		{
   662  			ShardMode: config.ShardPessimistic,
   663  			RouteRules: []*router.TableRule{
   664  				{
   665  					SchemaPattern: schema,
   666  					TargetSchema:  "db",
   667  					TablePattern:  "t_*",
   668  					TargetTable:   "t",
   669  				},
   670  			},
   671  			Mode:                config.ModeAll,
   672  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.ShardTableSchemaChecking: {}, config.ShardAutoIncrementIDChecking: {}}),
   673  		},
   674  	}
   675  
   676  	createTable1 := `CREATE TABLE %s (
   677  				  id int(11) NOT NULL AUTO_INCREMENT,
   678  				  b int(11) DEFAULT NULL,
   679  				PRIMARY KEY (id),
   680  				UNIQUE KEY u_b(b)
   681  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   682  
   683  	createTable2 := `CREATE TABLE %s (
   684  				  id int(11) NOT NULL,
   685  				  b int(11) DEFAULT NULL,
   686  				INDEX (id),
   687  				UNIQUE KEY u_b(b)
   688  				) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   689  
   690  	errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable}
   691  	createTableSQL := "SHOW CREATE TABLE `%s`.`%s`"
   692  	mock := initMockDB(t)
   693  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   694  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   695  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   696  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   697  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   698  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   699  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   700  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   701  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   702  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   703  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   704  	require.NoError(t, err)
   705  	require.Regexp(t, "(.|\n)*sourceID  table .* of sharding .* have auto-increment key(.|\n)*", msg)
   706  
   707  	mock = initMockDB(t)
   708  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   709  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   710  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   711  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   712  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   713  	mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   714  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   715  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   716  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   717  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   718  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   719  	require.NoError(t, err)
   720  	require.Regexp(t, "sourceID  table .* of sharding .* have auto-increment key", result.Results[0].Errors[0].ShortErr)
   721  
   722  	// happy path
   723  
   724  	checkHappyPath(t, func() {
   725  		mock := initMockDB(t)
   726  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   727  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   728  		mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   729  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   730  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1)))
   731  		mock.ExpectQuery("SHOW VARIABLES LIKE 'max_connections'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("max_connections", "2"))
   732  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   733  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb1)))
   734  		mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   735  		mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable2, tb2)))
   736  	}, cfgs)
   737  }
   738  
   739  func TestSameTargetTableDetection(t *testing.T) {
   740  	cfgs := []*config.SubTaskConfig{
   741  		{
   742  			RouteRules: []*router.TableRule{
   743  				{
   744  					SchemaPattern: schema,
   745  					TargetSchema:  "db",
   746  					TablePattern:  tb1,
   747  					TargetTable:   "t",
   748  				}, {
   749  					SchemaPattern: schema,
   750  					TargetSchema:  "db",
   751  					TablePattern:  tb2,
   752  					TargetTable:   "T",
   753  				},
   754  			},
   755  			Mode:                config.ModeAll,
   756  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.TableSchemaChecking: {}}),
   757  		},
   758  	}
   759  
   760  	createTable1 := `CREATE TABLE %s (
   761  				  	id int(11) NOT NULL AUTO_INCREMENT,
   762    					b int(11) DEFAULT NULL,
   763  					PRIMARY KEY (id),
   764  					UNIQUE KEY u_b(b)
   765  					) ENGINE=InnoDB DEFAULT CHARSET=latin1`
   766  	errNoSuchTable := &gmysql.MySQLError{Number: mysql.ErrNoSuchTable}
   767  	createTableSQL := "SHOW CREATE TABLE `%s`.`%s`"
   768  
   769  	mock := initMockDB(t)
   770  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   771  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   772  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   773  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   774  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   775  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   776  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   777  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   778  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   779  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   780  	require.ErrorContains(t, err, "same table name in case-insensitive")
   781  	require.Len(t, msg, 0)
   782  
   783  	mock = initMockDB(t)
   784  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   785  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   786  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   787  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LoaderCheckpoint(""))).WillReturnError(errNoSuchTable)
   788  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.LightningCheckpoint(""))).WillReturnError(errNoSuchTable)
   789  	mock.ExpectQuery(fmt.Sprintf(createTableSQL, "", cputil.SyncerCheckpoint(""))).WillReturnError(errNoSuchTable)
   790  	mock.ExpectQuery("SHOW VARIABLES LIKE 'sql_mode'").WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("sql_mode", ""))
   791  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb1)))
   792  	mock.ExpectQuery("SHOW CREATE TABLE .*").WillReturnRows(sqlmock.NewRows([]string{"Table", "Create Table"}).AddRow(tb1, fmt.Sprintf(createTable1, tb2)))
   793  	_, err = RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   794  	require.ErrorContains(t, err, "same table name in case-insensitive")
   795  }
   796  
   797  func TestMetaPositionChecking(t *testing.T) {
   798  	cfgs := []*config.SubTaskConfig{
   799  		{
   800  			Mode:                config.ModeIncrement,
   801  			UseRelay:            false,
   802  			Meta:                nil,
   803  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}),
   804  		},
   805  	}
   806  	checkHappyPath(t, func() {
   807  		_ = initMockDB(t)
   808  	}, cfgs)
   809  
   810  	cfgs = []*config.SubTaskConfig{
   811  		{
   812  			Mode:                config.ModeIncrement,
   813  			UseRelay:            false,
   814  			SyncerConfig:        config.SyncerConfig{EnableGTID: true},
   815  			Meta:                &config.Meta{},
   816  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}),
   817  		},
   818  	}
   819  	checkHappyPath(t, func() {
   820  		_ = initMockDB(t)
   821  	}, cfgs)
   822  
   823  	cfgs = []*config.SubTaskConfig{
   824  		{
   825  			Mode:                config.ModeIncrement,
   826  			UseRelay:            false,
   827  			Meta:                &config.Meta{BinLogGTID: "938bc44f-4acc-11ed-a147-0242ac110003:1-8"},
   828  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}),
   829  		},
   830  	}
   831  	checkHappyPath(t, func() {
   832  		_ = initMockDB(t)
   833  	}, cfgs)
   834  
   835  	cfgs = []*config.SubTaskConfig{
   836  		{
   837  			Mode:                config.ModeIncrement,
   838  			UseRelay:            true,
   839  			Meta:                &config.Meta{BinLogName: "mysql-bin.000001", BinLogPos: 123},
   840  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}),
   841  		},
   842  	}
   843  	checkHappyPath(t, func() {
   844  		_ = initMockDB(t)
   845  	}, cfgs)
   846  
   847  	cfgs = []*config.SubTaskConfig{
   848  		{
   849  			Mode:                config.ModeAll,
   850  			UseRelay:            false,
   851  			Meta:                &config.Meta{BinLogName: "mysql-bin.000001", BinLogPos: 123},
   852  			IgnoreCheckingItems: ignoreExcept(map[string]struct{}{config.MetaPositionChecking: {}}),
   853  		},
   854  	}
   855  	checkHappyPath(t, func() {
   856  		_ = initMockDB(t)
   857  	}, cfgs)
   858  }
   859  
   860  func initMockDB(t *testing.T) sqlmock.Sqlmock {
   861  	t.Helper()
   862  
   863  	mock, err := conn.MockDefaultDBProvider()
   864  	require.NoError(t, err)
   865  	mock.ExpectQuery("SHOW DATABASES").WillReturnRows(sqlmock.NewRows([]string{"DATABASE"}).AddRow(schema))
   866  	mock.ExpectQuery("SHOW FULL TABLES").WillReturnRows(sqlmock.NewRows([]string{"Tables_in_" + schema, "Table_type"}).AddRow(tb1, "BASE TABLE").AddRow(tb2, "BASE TABLE"))
   867  	return mock
   868  }
   869  
   870  func checkHappyPath(t *testing.T, pre func(), cfgs []*config.SubTaskConfig) {
   871  	t.Helper()
   872  
   873  	pre()
   874  	msg, err := CheckSyncConfig(context.Background(), cfgs, common.DefaultErrorCnt, common.DefaultWarnCnt)
   875  	require.NoError(t, err)
   876  	require.Equal(t, CheckTaskSuccess, msg)
   877  
   878  	pre()
   879  	result, err := RunCheckOnConfigs(context.Background(), cfgs, false, 100, 100)
   880  	require.NoError(t, err)
   881  	require.True(t, result.Summary.Passed)
   882  	require.Equal(t, int64(0), result.Summary.Warning)
   883  }