github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/schema_storage_test.go (about)

     1  // Copyright 2020 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 entry
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"fmt"
    20  	"testing"
    21  
    22  	"github.com/pingcap/errors"
    23  	"github.com/pingcap/log"
    24  	ticonfig "github.com/pingcap/tidb/pkg/config"
    25  	"github.com/pingcap/tidb/pkg/ddl"
    26  	"github.com/pingcap/tidb/pkg/domain"
    27  	tidbkv "github.com/pingcap/tidb/pkg/kv"
    28  	timeta "github.com/pingcap/tidb/pkg/meta"
    29  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    30  	"github.com/pingcap/tidb/pkg/parser/mysql"
    31  	"github.com/pingcap/tidb/pkg/session"
    32  	"github.com/pingcap/tidb/pkg/sessionctx"
    33  	"github.com/pingcap/tidb/pkg/store/mockstore"
    34  	"github.com/pingcap/tidb/pkg/testkit"
    35  	"github.com/pingcap/tidb/pkg/types"
    36  	"github.com/pingcap/tiflow/cdc/entry/schema"
    37  	"github.com/pingcap/tiflow/cdc/kv"
    38  	"github.com/pingcap/tiflow/cdc/model"
    39  	"github.com/pingcap/tiflow/pkg/config"
    40  	"github.com/pingcap/tiflow/pkg/filter"
    41  	"github.com/pingcap/tiflow/pkg/util"
    42  	"github.com/stretchr/testify/require"
    43  	"github.com/tikv/client-go/v2/oracle"
    44  	"go.uber.org/zap"
    45  )
    46  
    47  func TestSchema(t *testing.T) {
    48  	dbName := timodel.NewCIStr("Test")
    49  	// db and ignoreDB info
    50  	dbInfo := &timodel.DBInfo{
    51  		ID:    1,
    52  		Name:  dbName,
    53  		State: timodel.StatePublic,
    54  	}
    55  	// `createSchema` job1
    56  	job := &timodel.Job{
    57  		ID:         3,
    58  		State:      timodel.JobStateDone,
    59  		SchemaID:   1,
    60  		Type:       timodel.ActionCreateSchema,
    61  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123},
    62  		Query:      "create database test",
    63  	}
    64  	// reconstruct the local schema
    65  	snap := schema.NewEmptySnapshot(false)
    66  	err := snap.HandleDDL(job)
    67  	require.Nil(t, err)
    68  	_, exist := snap.SchemaByID(job.SchemaID)
    69  	require.True(t, exist)
    70  
    71  	// test drop schema
    72  	job = &timodel.Job{
    73  		ID:         6,
    74  		State:      timodel.JobStateDone,
    75  		SchemaID:   1,
    76  		Type:       timodel.ActionDropSchema,
    77  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, DBInfo: dbInfo, FinishedTS: 124},
    78  		Query:      "drop database test",
    79  	}
    80  	err = snap.HandleDDL(job)
    81  	require.Nil(t, err)
    82  	_, exist = snap.SchemaByID(job.SchemaID)
    83  	require.False(t, exist)
    84  
    85  	job = &timodel.Job{
    86  		ID:         3,
    87  		State:      timodel.JobStateDone,
    88  		SchemaID:   1,
    89  		Type:       timodel.ActionCreateSchema,
    90  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, DBInfo: dbInfo, FinishedTS: 124},
    91  		Query:      "create database test",
    92  	}
    93  
    94  	err = snap.HandleDDL(job)
    95  	require.Nil(t, err)
    96  	err = snap.HandleDDL(job)
    97  	require.True(t, errors.IsAlreadyExists(err))
    98  
    99  	// test schema drop schema error
   100  	job = &timodel.Job{
   101  		ID:         9,
   102  		State:      timodel.JobStateDone,
   103  		SchemaID:   1,
   104  		Type:       timodel.ActionDropSchema,
   105  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123},
   106  		Query:      "drop database test",
   107  	}
   108  	err = snap.HandleDDL(job)
   109  	require.Nil(t, err)
   110  	err = snap.HandleDDL(job)
   111  	require.True(t, errors.IsNotFound(err))
   112  }
   113  
   114  func TestTable(t *testing.T) {
   115  	var jobs []*timodel.Job
   116  	dbName := timodel.NewCIStr("Test")
   117  	tbName := timodel.NewCIStr("T")
   118  	colName := timodel.NewCIStr("A")
   119  	idxName := timodel.NewCIStr("idx")
   120  	// column info
   121  	colInfo := &timodel.ColumnInfo{
   122  		ID:        1,
   123  		Name:      colName,
   124  		Offset:    0,
   125  		FieldType: *types.NewFieldType(mysql.TypeLonglong),
   126  		State:     timodel.StatePublic,
   127  	}
   128  	// index info
   129  	idxInfo := &timodel.IndexInfo{
   130  		Name:  idxName,
   131  		Table: tbName,
   132  		Columns: []*timodel.IndexColumn{
   133  			{
   134  				Name:   colName,
   135  				Offset: 0,
   136  				Length: 10,
   137  			},
   138  		},
   139  		Unique:  false,
   140  		Primary: false,
   141  		State:   timodel.StatePublic,
   142  	}
   143  	// table info
   144  	tblInfo := &timodel.TableInfo{
   145  		ID:    2,
   146  		Name:  tbName,
   147  		State: timodel.StatePublic,
   148  	}
   149  	// db info
   150  	dbInfo := &timodel.DBInfo{
   151  		ID:    3,
   152  		Name:  dbName,
   153  		State: timodel.StatePublic,
   154  	}
   155  
   156  	// `createSchema` job
   157  	job := &timodel.Job{
   158  		ID:         5,
   159  		State:      timodel.JobStateDone,
   160  		SchemaID:   3,
   161  		Type:       timodel.ActionCreateSchema,
   162  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 123},
   163  		Query:      "create database " + dbName.O,
   164  	}
   165  	jobs = append(jobs, job)
   166  
   167  	// `createTable` job
   168  	job = &timodel.Job{
   169  		ID:         6,
   170  		State:      timodel.JobStateDone,
   171  		SchemaID:   3,
   172  		TableID:    2,
   173  		Type:       timodel.ActionCreateTable,
   174  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, TableInfo: tblInfo, FinishedTS: 124},
   175  		Query:      "create table " + tbName.O,
   176  	}
   177  	jobs = append(jobs, job)
   178  
   179  	// `addColumn` job
   180  	tblInfo.Columns = []*timodel.ColumnInfo{colInfo}
   181  	job = &timodel.Job{
   182  		ID:         7,
   183  		State:      timodel.JobStateDone,
   184  		SchemaID:   3,
   185  		TableID:    2,
   186  		Type:       timodel.ActionAddColumn,
   187  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, TableInfo: tblInfo, FinishedTS: 125},
   188  		Query:      "alter table " + tbName.O + " add column " + colName.O,
   189  	}
   190  	jobs = append(jobs, job)
   191  
   192  	// construct a historical `addIndex` job
   193  	tblInfo = tblInfo.Clone()
   194  	tblInfo.Indices = []*timodel.IndexInfo{idxInfo}
   195  	job = &timodel.Job{
   196  		ID:         8,
   197  		State:      timodel.JobStateDone,
   198  		SchemaID:   3,
   199  		TableID:    2,
   200  		Type:       timodel.ActionAddIndex,
   201  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, TableInfo: tblInfo, FinishedTS: 126},
   202  		Query:      fmt.Sprintf("alter table %s add index %s(%s)", tbName, idxName, colName),
   203  	}
   204  	jobs = append(jobs, job)
   205  
   206  	// reconstruct the local schema
   207  	snap := schema.NewEmptySnapshot(false)
   208  	for _, job := range jobs {
   209  		err := snap.HandleDDL(job)
   210  		require.Nil(t, err)
   211  	}
   212  
   213  	// check the historical db that constructed above whether in the schema list of local schema
   214  	_, ok := snap.SchemaByID(dbInfo.ID)
   215  	require.True(t, ok)
   216  	// check the historical table that constructed above whether in the table list of local schema
   217  	table, ok := snap.PhysicalTableByID(tblInfo.ID)
   218  	require.True(t, ok)
   219  	require.Len(t, table.Columns, 1)
   220  	require.Len(t, table.Indices, 1)
   221  
   222  	// test ineligible tables
   223  	require.True(t, snap.IsIneligibleTableID(2))
   224  
   225  	// check truncate table
   226  	tblInfo1 := &timodel.TableInfo{
   227  		ID:    9,
   228  		Name:  tbName,
   229  		State: timodel.StatePublic,
   230  	}
   231  	job = &timodel.Job{
   232  		ID:         9,
   233  		State:      timodel.JobStateDone,
   234  		SchemaID:   3,
   235  		TableID:    2,
   236  		Type:       timodel.ActionTruncateTable,
   237  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, TableInfo: tblInfo1, FinishedTS: 127},
   238  		Query:      "truncate table " + tbName.O,
   239  	}
   240  	preTableInfo, err := snap.PreTableInfo(job)
   241  	require.Nil(t, err)
   242  	require.Equal(t, preTableInfo.TableName, model.TableName{Schema: "Test", Table: "T", TableID: 2})
   243  	require.Equal(t, preTableInfo.ID, int64(2))
   244  
   245  	err = snap.HandleDDL(job)
   246  	require.Nil(t, err)
   247  
   248  	_, ok = snap.PhysicalTableByID(tblInfo1.ID)
   249  	require.True(t, ok)
   250  
   251  	_, ok = snap.PhysicalTableByID(2)
   252  	require.False(t, ok)
   253  
   254  	// test ineligible tables
   255  	require.True(t, snap.IsIneligibleTableID(9))
   256  	require.False(t, snap.IsIneligibleTableID(2))
   257  	// check drop table
   258  	job = &timodel.Job{
   259  		ID:         9,
   260  		State:      timodel.JobStateDone,
   261  		SchemaID:   3,
   262  		TableID:    9,
   263  		Type:       timodel.ActionDropTable,
   264  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 6, FinishedTS: 128},
   265  		Query:      "drop table " + tbName.O,
   266  	}
   267  	preTableInfo, err = snap.PreTableInfo(job)
   268  	require.Nil(t, err)
   269  	require.Equal(t, preTableInfo.TableName, model.TableName{Schema: "Test", Table: "T", TableID: 9})
   270  	require.Equal(t, preTableInfo.ID, int64(9))
   271  
   272  	err = snap.HandleDDL(job)
   273  	require.Nil(t, err)
   274  
   275  	_, ok = snap.PhysicalTableByID(tblInfo.ID)
   276  	require.False(t, ok)
   277  
   278  	// test ineligible tables
   279  	require.False(t, snap.IsIneligibleTableID(9))
   280  
   281  	// drop schema
   282  	job = &timodel.Job{
   283  		ID:         10,
   284  		State:      timodel.JobStateDone,
   285  		SchemaID:   3,
   286  		Type:       timodel.ActionDropSchema,
   287  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 7, FinishedTS: 129},
   288  		Query:      "drop table " + dbName.O,
   289  	}
   290  	err = snap.DoHandleDDL(job)
   291  	require.Nil(t, err)
   292  }
   293  
   294  func TestHandleDDL(t *testing.T) {
   295  	snap := schema.NewEmptySnapshot(false)
   296  	dbName := timodel.NewCIStr("Test")
   297  	colName := timodel.NewCIStr("A")
   298  	tbName := timodel.NewCIStr("T")
   299  	newTbName := timodel.NewCIStr("RT")
   300  
   301  	// db info
   302  	dbInfo := &timodel.DBInfo{
   303  		ID:    2,
   304  		Name:  dbName,
   305  		State: timodel.StatePublic,
   306  	}
   307  	// table Info
   308  	tblInfo := &timodel.TableInfo{
   309  		ID:    6,
   310  		Name:  tbName,
   311  		State: timodel.StatePublic,
   312  	}
   313  	// column info
   314  	colInfo := &timodel.ColumnInfo{
   315  		ID:        8,
   316  		Name:      colName,
   317  		Offset:    0,
   318  		FieldType: *types.NewFieldType(mysql.TypeLonglong),
   319  		State:     timodel.StatePublic,
   320  	}
   321  	tblInfo.Columns = []*timodel.ColumnInfo{colInfo}
   322  
   323  	testCases := []struct {
   324  		name        string
   325  		jobID       int64
   326  		schemaID    int64
   327  		tableID     int64
   328  		jobType     timodel.ActionType
   329  		binlogInfo  *timodel.HistoryInfo
   330  		query       string
   331  		resultQuery string
   332  		schemaName  string
   333  		tableName   string
   334  	}{
   335  		{name: "createSchema", jobID: 3, schemaID: 2, tableID: 0, jobType: timodel.ActionCreateSchema, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "create database Test", resultQuery: "create database Test", schemaName: dbInfo.Name.O, tableName: ""},
   336  		{name: "updateSchema", jobID: 4, schemaID: 2, tableID: 0, jobType: timodel.ActionModifySchemaCharsetAndCollate, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 8, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "ALTER DATABASE Test CHARACTER SET utf8mb4;", resultQuery: "ALTER DATABASE Test CHARACTER SET utf8mb4;", schemaName: dbInfo.Name.O},
   337  		{name: "createTable", jobID: 7, schemaID: 2, tableID: 6, jobType: timodel.ActionCreateTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "create table T(id int);", resultQuery: "create table T(id int);", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O},
   338  		{name: "addColumn", jobID: 9, schemaID: 2, tableID: 6, jobType: timodel.ActionAddColumn, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "alter table T add a varchar(45);", resultQuery: "alter table T add a varchar(45);", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O},
   339  		{name: "truncateTable", jobID: 10, schemaID: 2, tableID: 6, jobType: timodel.ActionTruncateTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "truncate table T;", resultQuery: "truncate table T;", schemaName: dbInfo.Name.O, tableName: tblInfo.Name.O},
   340  		{name: "renameTable", jobID: 11, schemaID: 2, tableID: 10, jobType: timodel.ActionRenameTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 6, DBInfo: nil, TableInfo: tblInfo, FinishedTS: 123}, query: "rename table T to RT;", resultQuery: "rename table T to RT;", schemaName: dbInfo.Name.O, tableName: newTbName.O},
   341  		{name: "dropTable", jobID: 12, schemaID: 2, tableID: 12, jobType: timodel.ActionDropTable, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 7, DBInfo: nil, TableInfo: nil, FinishedTS: 123}, query: "drop table RT;", resultQuery: "drop table RT;", schemaName: dbInfo.Name.O, tableName: newTbName.O},
   342  		{name: "dropSchema", jobID: 13, schemaID: 2, tableID: 0, jobType: timodel.ActionDropSchema, binlogInfo: &timodel.HistoryInfo{SchemaVersion: 8, DBInfo: dbInfo, TableInfo: nil, FinishedTS: 123}, query: "drop database test;", resultQuery: "drop database test;", schemaName: dbInfo.Name.O, tableName: ""},
   343  	}
   344  
   345  	for _, testCase := range testCases {
   346  		// prepare for ddl
   347  		switch testCase.name {
   348  		case "addColumn":
   349  			tblInfo.Columns = []*timodel.ColumnInfo{colInfo}
   350  		case "truncateTable":
   351  			tblInfo.ID = 10
   352  		case "renameTable":
   353  			tblInfo.ID = 12
   354  			tblInfo.Name = newTbName
   355  		}
   356  
   357  		job := &timodel.Job{
   358  			ID:         testCase.jobID,
   359  			State:      timodel.JobStateDone,
   360  			SchemaID:   testCase.schemaID,
   361  			TableID:    testCase.tableID,
   362  			Type:       testCase.jobType,
   363  			BinlogInfo: testCase.binlogInfo,
   364  			Query:      testCase.query,
   365  		}
   366  		testDoDDLAndCheck(t, snap, job, false)
   367  
   368  		// custom check after ddl
   369  		switch testCase.name {
   370  		case "createSchema":
   371  			_, ok := snap.SchemaByID(dbInfo.ID)
   372  			require.True(t, ok)
   373  		case "createTable":
   374  			_, ok := snap.PhysicalTableByID(tblInfo.ID)
   375  			require.True(t, ok)
   376  		case "renameTable":
   377  			tb, ok := snap.PhysicalTableByID(tblInfo.ID)
   378  			require.True(t, ok)
   379  			require.Equal(t, tblInfo.Name, tb.Name)
   380  		case "addColumn", "truncateTable":
   381  			tb, ok := snap.PhysicalTableByID(tblInfo.ID)
   382  			require.True(t, ok)
   383  			require.Len(t, tb.Columns, 1)
   384  		case "dropTable":
   385  			_, ok := snap.PhysicalTableByID(tblInfo.ID)
   386  			require.False(t, ok)
   387  		case "dropSchema":
   388  			_, ok := snap.SchemaByID(job.SchemaID)
   389  			require.False(t, ok)
   390  		}
   391  	}
   392  }
   393  
   394  func TestHandleRenameTables(t *testing.T) {
   395  	// Initial schema: db_1.table_1 and db_2.table_2.
   396  	snap := schema.NewEmptySnapshot(true)
   397  	var i int64
   398  	for i = 1; i < 3; i++ {
   399  		dbInfo := &timodel.DBInfo{
   400  			ID:    i,
   401  			Name:  timodel.NewCIStr(fmt.Sprintf("db_%d", i)),
   402  			State: timodel.StatePublic,
   403  		}
   404  		job := &timodel.Job{
   405  			ID:         i,
   406  			State:      timodel.JobStateDone,
   407  			SchemaID:   i,
   408  			Type:       timodel.ActionCreateSchema,
   409  			BinlogInfo: &timodel.HistoryInfo{SchemaVersion: i, DBInfo: dbInfo, FinishedTS: uint64(i)},
   410  			Query:      fmt.Sprintf("create database %s", dbInfo.Name.O),
   411  		}
   412  		err := snap.HandleDDL(job)
   413  		require.Nil(t, err)
   414  	}
   415  	for i = 1; i < 3; i++ {
   416  		tblInfo := &timodel.TableInfo{
   417  			ID:    10 + i,
   418  			Name:  timodel.NewCIStr(fmt.Sprintf("table_%d", i)),
   419  			State: timodel.StatePublic,
   420  		}
   421  		job := &timodel.Job{
   422  			ID:         i,
   423  			State:      timodel.JobStateDone,
   424  			SchemaID:   i,
   425  			TableID:    10 + i,
   426  			Type:       timodel.ActionCreateTable,
   427  			BinlogInfo: &timodel.HistoryInfo{SchemaVersion: i, TableInfo: tblInfo, FinishedTS: uint64(10 + i)},
   428  			Query:      "create table " + tblInfo.Name.O,
   429  		}
   430  		err := snap.HandleDDL(job)
   431  		require.Nil(t, err)
   432  	}
   433  
   434  	// rename table db1.table_1 to db2.x, db2.table_2 to db1.y
   435  	oldSchemaIDs := []int64{1, 2}
   436  	newSchemaIDs := []int64{2, 1}
   437  	oldTableIDs := []int64{11, 12}
   438  	newTableNames := []timodel.CIStr{timodel.NewCIStr("x"), timodel.NewCIStr("y")}
   439  	oldSchemaNames := []timodel.CIStr{timodel.NewCIStr("db_1"), timodel.NewCIStr("db_2")}
   440  	args := []interface{}{oldSchemaIDs, newSchemaIDs, newTableNames, oldTableIDs, oldSchemaNames}
   441  	rawArgs, err := json.Marshal(args)
   442  	require.Nil(t, err)
   443  	var job *timodel.Job = &timodel.Job{
   444  		Type:    timodel.ActionRenameTables,
   445  		RawArgs: rawArgs,
   446  		BinlogInfo: &timodel.HistoryInfo{
   447  			FinishedTS: 11112222,
   448  		},
   449  	}
   450  	job.BinlogInfo.MultipleTableInfos = append(job.BinlogInfo.MultipleTableInfos,
   451  		&timodel.TableInfo{
   452  			ID:    13,
   453  			Name:  timodel.NewCIStr("x"),
   454  			State: timodel.StatePublic,
   455  		})
   456  	job.BinlogInfo.MultipleTableInfos = append(job.BinlogInfo.MultipleTableInfos,
   457  		&timodel.TableInfo{
   458  			ID:    14,
   459  			Name:  timodel.NewCIStr("y"),
   460  			State: timodel.StatePublic,
   461  		})
   462  	testDoDDLAndCheck(t, snap, job, false)
   463  
   464  	var ok bool
   465  	_, ok = snap.PhysicalTableByID(13)
   466  	require.True(t, ok)
   467  	_, ok = snap.PhysicalTableByID(14)
   468  	require.True(t, ok)
   469  	_, ok = snap.PhysicalTableByID(11)
   470  	require.False(t, ok)
   471  	_, ok = snap.PhysicalTableByID(12)
   472  	require.False(t, ok)
   473  
   474  	n1, _ := snap.TableIDByName("db_2", "x")
   475  	require.Equal(t, n1, int64(13))
   476  	n2, _ := snap.TableIDByName("db_1", "y")
   477  	require.Equal(t, n2, int64(14))
   478  	require.Equal(t, uint64(11112222), snap.CurrentTs())
   479  }
   480  
   481  func testDoDDLAndCheck(t *testing.T, snap *schema.Snapshot, job *timodel.Job, isErr bool) {
   482  	err := snap.HandleDDL(job)
   483  	require.Equal(t, err != nil, isErr)
   484  }
   485  
   486  func TestMultiVersionStorage(t *testing.T) {
   487  	ctx, cancel := context.WithCancel(context.Background())
   488  	dbName := timodel.NewCIStr("Test")
   489  	tbName := timodel.NewCIStr("T1")
   490  	// db and ignoreDB info
   491  	dbInfo := &timodel.DBInfo{
   492  		ID:    11,
   493  		Name:  dbName,
   494  		State: timodel.StatePublic,
   495  	}
   496  	var jobs []*timodel.Job
   497  	// `createSchema` job1
   498  	job := &timodel.Job{
   499  		ID:         13,
   500  		State:      timodel.JobStateDone,
   501  		SchemaID:   11,
   502  		Type:       timodel.ActionCreateSchema,
   503  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 1, DBInfo: dbInfo, FinishedTS: 100},
   504  		Query:      "create database test",
   505  	}
   506  	jobs = append(jobs, job)
   507  
   508  	// table info
   509  	tblInfo := &timodel.TableInfo{
   510  		ID:    12,
   511  		Name:  tbName,
   512  		State: timodel.StatePublic,
   513  	}
   514  
   515  	// `createTable` job
   516  	job = &timodel.Job{
   517  		ID:         16,
   518  		State:      timodel.JobStateDone,
   519  		SchemaID:   11,
   520  		TableID:    12,
   521  		Type:       timodel.ActionCreateTable,
   522  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 2, TableInfo: tblInfo, FinishedTS: 110},
   523  		Query:      "create table " + tbName.O,
   524  	}
   525  
   526  	jobs = append(jobs, job)
   527  
   528  	tbName = timodel.NewCIStr("T2")
   529  	// table info
   530  	tblInfo = &timodel.TableInfo{
   531  		ID:    13,
   532  		Name:  tbName,
   533  		State: timodel.StatePublic,
   534  	}
   535  	// `createTable` job
   536  	job = &timodel.Job{
   537  		ID:         16,
   538  		State:      timodel.JobStateDone,
   539  		SchemaID:   11,
   540  		TableID:    13,
   541  		Type:       timodel.ActionCreateTable,
   542  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 3, TableInfo: tblInfo, FinishedTS: 120},
   543  		Query:      "create table " + tbName.O,
   544  	}
   545  
   546  	jobs = append(jobs, job)
   547  	f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   548  	require.Nil(t, err)
   549  	storage, err := NewSchemaStorage(nil, 0, false, model.DefaultChangeFeedID("dummy"), util.RoleTester, f)
   550  	require.Nil(t, err)
   551  	for _, job := range jobs {
   552  		err := storage.HandleDDLJob(job)
   553  		require.Nil(t, err)
   554  	}
   555  
   556  	// `dropTable` job
   557  	job = &timodel.Job{
   558  		ID:         16,
   559  		State:      timodel.JobStateDone,
   560  		SchemaID:   11,
   561  		TableID:    12,
   562  		Type:       timodel.ActionDropTable,
   563  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 4, FinishedTS: 130},
   564  	}
   565  
   566  	err = storage.HandleDDLJob(job)
   567  	require.Nil(t, err)
   568  
   569  	// `dropSchema` job
   570  	job = &timodel.Job{
   571  		ID:         16,
   572  		State:      timodel.JobStateDone,
   573  		SchemaID:   11,
   574  		Type:       timodel.ActionDropSchema,
   575  		BinlogInfo: &timodel.HistoryInfo{SchemaVersion: 5, FinishedTS: 140, DBInfo: dbInfo},
   576  	}
   577  
   578  	err = storage.HandleDDLJob(job)
   579  	require.Nil(t, err)
   580  
   581  	require.Equal(t, storage.(*schemaStorage).resolvedTs, uint64(140))
   582  	snap, err := storage.GetSnapshot(ctx, 100)
   583  	require.Nil(t, err)
   584  	_, exist := snap.SchemaByID(11)
   585  	require.True(t, exist)
   586  	_, exist = snap.PhysicalTableByID(12)
   587  	require.False(t, exist)
   588  	_, exist = snap.PhysicalTableByID(13)
   589  	require.False(t, exist)
   590  
   591  	snap, err = storage.GetSnapshot(ctx, 115)
   592  	require.Nil(t, err)
   593  	_, exist = snap.SchemaByID(11)
   594  	require.True(t, exist)
   595  	_, exist = snap.PhysicalTableByID(12)
   596  	require.True(t, exist)
   597  	_, exist = snap.PhysicalTableByID(13)
   598  	require.False(t, exist)
   599  
   600  	snap, err = storage.GetSnapshot(ctx, 125)
   601  	require.Nil(t, err)
   602  	_, exist = snap.SchemaByID(11)
   603  	require.True(t, exist)
   604  	_, exist = snap.PhysicalTableByID(12)
   605  	require.True(t, exist)
   606  	_, exist = snap.PhysicalTableByID(13)
   607  	require.True(t, exist)
   608  
   609  	snap, err = storage.GetSnapshot(ctx, 135)
   610  	require.Nil(t, err)
   611  	_, exist = snap.SchemaByID(11)
   612  	require.True(t, exist)
   613  	_, exist = snap.PhysicalTableByID(12)
   614  	require.False(t, exist)
   615  	_, exist = snap.PhysicalTableByID(13)
   616  	require.True(t, exist)
   617  
   618  	snap, err = storage.GetSnapshot(ctx, 140)
   619  	require.Nil(t, err)
   620  	_, exist = snap.SchemaByID(11)
   621  	require.False(t, exist)
   622  	_, exist = snap.PhysicalTableByID(12)
   623  	require.False(t, exist)
   624  	_, exist = snap.PhysicalTableByID(13)
   625  	require.False(t, exist)
   626  
   627  	lastSchemaTs := storage.DoGC(0)
   628  	require.Equal(t, uint64(0), lastSchemaTs)
   629  
   630  	snap, err = storage.GetSnapshot(ctx, 100)
   631  	require.Nil(t, err)
   632  	_, exist = snap.SchemaByID(11)
   633  	require.True(t, exist)
   634  	_, exist = snap.PhysicalTableByID(12)
   635  	require.False(t, exist)
   636  	_, exist = snap.PhysicalTableByID(13)
   637  	require.False(t, exist)
   638  	storage.DoGC(115)
   639  	_, err = storage.GetSnapshot(ctx, 100)
   640  	require.NotNil(t, err)
   641  	snap, err = storage.GetSnapshot(ctx, 115)
   642  	require.Nil(t, err)
   643  	_, exist = snap.SchemaByID(11)
   644  	require.True(t, exist)
   645  	_, exist = snap.PhysicalTableByID(12)
   646  	require.True(t, exist)
   647  	_, exist = snap.PhysicalTableByID(13)
   648  	require.False(t, exist)
   649  
   650  	lastSchemaTs = storage.DoGC(155)
   651  	require.Equal(t, uint64(140), lastSchemaTs)
   652  
   653  	storage.AdvanceResolvedTs(185)
   654  
   655  	snap, err = storage.GetSnapshot(ctx, 180)
   656  	require.Nil(t, err)
   657  	_, exist = snap.SchemaByID(11)
   658  	require.False(t, exist)
   659  	_, exist = snap.PhysicalTableByID(12)
   660  	require.False(t, exist)
   661  	_, exist = snap.PhysicalTableByID(13)
   662  	require.False(t, exist)
   663  	_, err = storage.GetSnapshot(ctx, 130)
   664  	require.NotNil(t, err)
   665  
   666  	cancel()
   667  	_, err = storage.GetSnapshot(ctx, 200)
   668  	require.Equal(t, errors.Cause(err), context.Canceled)
   669  }
   670  
   671  func TestCreateSnapFromMeta(t *testing.T) {
   672  	store, err := mockstore.NewMockStore()
   673  	require.Nil(t, err)
   674  	defer store.Close() //nolint:errcheck
   675  
   676  	session.SetSchemaLease(0)
   677  	session.DisableStats4Test()
   678  	domain, err := session.BootstrapSession(store)
   679  	require.Nil(t, err)
   680  	defer domain.Close()
   681  	domain.SetStatsUpdating(true)
   682  	tk := testkit.NewTestKit(t, store)
   683  	tk.MustExec("create database test2")
   684  	tk.MustExec("create table test.simple_test1 (id bigint primary key)")
   685  	tk.MustExec("create table test.simple_test2 (id bigint primary key)")
   686  	tk.MustExec("create table test2.simple_test3 (id bigint primary key)")
   687  	tk.MustExec("create table test2.simple_test4 (id bigint primary key)")
   688  	tk.MustExec("create table test2.simple_test5 (a bigint)")
   689  	ver, err := store.CurrentVersion(oracle.GlobalTxnScope)
   690  	require.Nil(t, err)
   691  	meta := kv.GetSnapshotMeta(store, ver.Ver)
   692  	f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   693  	require.Nil(t, err)
   694  	snap, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ver.Ver, false, f)
   695  	require.Nil(t, err)
   696  	_, ok := snap.TableByName("test", "simple_test1")
   697  	require.True(t, ok)
   698  	tableID, ok := snap.TableIDByName("test2", "simple_test5")
   699  	require.True(t, ok)
   700  	require.True(t, snap.IsIneligibleTableID(tableID))
   701  	dbInfo, ok := snap.SchemaByTableID(tableID)
   702  	require.True(t, ok)
   703  	require.Equal(t, dbInfo.Name.O, "test2")
   704  }
   705  
   706  func TestExplicitTables(t *testing.T) {
   707  	store, err := mockstore.NewMockStore()
   708  	require.Nil(t, err)
   709  	defer store.Close() //nolint:errcheck
   710  
   711  	session.SetSchemaLease(0)
   712  	session.DisableStats4Test()
   713  	domain, err := session.BootstrapSession(store)
   714  	require.Nil(t, err)
   715  	defer domain.Close()
   716  	domain.SetStatsUpdating(true)
   717  	tk := testkit.NewTestKit(t, store)
   718  	ver1, err := store.CurrentVersion(oracle.GlobalTxnScope)
   719  	require.Nil(t, err)
   720  	tk.MustExec("create database test2")
   721  	tk.MustExec("create table test.simple_test1 (id bigint primary key)")
   722  	tk.MustExec("create table test.simple_test2 (id bigint unique key)")
   723  	tk.MustExec("create table test2.simple_test3 (a bigint)")
   724  	tk.MustExec("create table test2.simple_test4 (a varchar(20) unique key)")
   725  	tk.MustExec("create table test2.simple_test5 (a varchar(20))")
   726  	ver2, err := store.CurrentVersion(oracle.GlobalTxnScope)
   727  	require.Nil(t, err)
   728  	meta1 := kv.GetSnapshotMeta(store, ver1.Ver)
   729  	f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   730  	require.Nil(t, err)
   731  	snap1, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta1, ver1.Ver, true /* forceReplicate */, f)
   732  	require.Nil(t, err)
   733  	meta2 := kv.GetSnapshotMeta(store, ver2.Ver)
   734  	snap2, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta2, ver2.Ver, false /* forceReplicate */, f)
   735  	require.Nil(t, err)
   736  	snap3, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta2, ver2.Ver, true /* forceReplicate */, f)
   737  	require.Nil(t, err)
   738  
   739  	// we don't need to count system tables since TiCDC
   740  	// don't replicate them and TiDB change them frequently,
   741  	// so we don't need to consider them in the table count
   742  	systemTablesFilter := func(dbName, tableName string) bool {
   743  		return dbName != "mysql" && dbName != "information_schema"
   744  	}
   745  	require.Equal(t, 5, snap2.TableCount(true,
   746  		systemTablesFilter)-snap1.TableCount(true, systemTablesFilter))
   747  	// only test simple_test1 included
   748  	require.Equal(t, 1, snap2.TableCount(false, systemTablesFilter))
   749  
   750  	require.Equal(t, 5, snap3.TableCount(true,
   751  		systemTablesFilter)-snap1.TableCount(true, systemTablesFilter))
   752  	// since we create a snapshot from meta2 and forceReplicate is true, so all tables are included
   753  	require.Equal(t, 5, snap3.TableCount(false, systemTablesFilter))
   754  }
   755  
   756  /*
   757  TODO: Untested Action:
   758  
   759  ActionAddForeignKey                 ActionType = 9
   760  ActionDropForeignKey                ActionType = 10
   761  ActionRebaseAutoID                  ActionType = 13
   762  ActionShardRowID                    ActionType = 16
   763  ActionLockTable                     ActionType = 27
   764  ActionUnlockTable                   ActionType = 28
   765  ActionRepairTable                   ActionType = 29
   766  ActionSetTiFlashReplica             ActionType = 30
   767  ActionUpdateTiFlashReplicaStatus    ActionType = 31
   768  ActionCreateSequence                ActionType = 34
   769  ActionAlterSequence                 ActionType = 35
   770  ActionDropSequence                  ActionType = 36
   771  ActionModifyTableAutoIdCache        ActionType = 39
   772  ActionRebaseAutoRandomBase          ActionType = 40
   773  ActionExchangeTablePartition        ActionType = 42
   774  ActionAddCheckConstraint            ActionType = 43
   775  ActionDropCheckConstraint           ActionType = 44
   776  ActionAlterCheckConstraint          ActionType = 45
   777  ActionAlterTableAlterPartition      ActionType = 46
   778  
   779  ... Any Action which of value is greater than 46 ...
   780  */
   781  func TestSchemaStorage(t *testing.T) {
   782  	ctx := context.Background()
   783  	testCases := [][]string{{
   784  		"create database test_ddl1",                                                                            // ActionCreateSchema
   785  		"create table test_ddl1.simple_test1 (id bigint primary key)",                                          // ActionCreateTable
   786  		"create table test_ddl1.simple_test2 (id bigint)",                                                      // ActionCreateTable
   787  		"create table test_ddl1.simple_test3 (id bigint primary key)",                                          // ActionCreateTable
   788  		"create table test_ddl1.simple_test4 (id bigint primary key)",                                          // ActionCreateTable
   789  		"DROP TABLE test_ddl1.simple_test3",                                                                    // ActionDropTable
   790  		"ALTER TABLE test_ddl1.simple_test1 ADD COLUMN c1 INT NOT NULL",                                        // ActionAddColumn
   791  		"ALTER TABLE test_ddl1.simple_test1 ADD c2 INT NOT NULL AFTER id",                                      // ActionAddColumn
   792  		"ALTER TABLE test_ddl1.simple_test1 ADD c3 INT NOT NULL, ADD c4 INT NOT NULL",                          // ActionAddColumns
   793  		"ALTER TABLE test_ddl1.simple_test1 DROP c1",                                                           // ActionDropColumn
   794  		"ALTER TABLE test_ddl1.simple_test1 DROP c2, DROP c3",                                                  // ActionDropColumns
   795  		"ALTER TABLE test_ddl1.simple_test1 ADD INDEX (c4)",                                                    // ActionAddIndex
   796  		"ALTER TABLE test_ddl1.simple_test1 DROP INDEX c4",                                                     // ActionDropIndex
   797  		"TRUNCATE test_ddl1.simple_test1",                                                                      // ActionTruncateTable
   798  		"ALTER DATABASE test_ddl1 CHARACTER SET = binary COLLATE binary",                                       // ActionModifySchemaCharsetAndCollate
   799  		"ALTER TABLE test_ddl1.simple_test2 ADD c1 INT NOT NULL, ADD c2 INT NOT NULL",                          // ActionAddColumns
   800  		"ALTER TABLE test_ddl1.simple_test2 ADD INDEX (c1)",                                                    // ActionAddIndex
   801  		"ALTER TABLE test_ddl1.simple_test2 ALTER INDEX c1 INVISIBLE",                                          // ActionAlterIndexVisibility
   802  		"ALTER TABLE test_ddl1.simple_test2 RENAME INDEX c1 TO idx_c1",                                         // ActionRenameIndex
   803  		"ALTER TABLE test_ddl1.simple_test2 MODIFY c2 BIGINT",                                                  // ActionModifyColumn
   804  		"CREATE VIEW test_ddl1.view_test2 AS SELECT * FROM test_ddl1.simple_test2 WHERE id > 2",                // ActionCreateView
   805  		"DROP VIEW test_ddl1.view_test2",                                                                       // ActionDropView
   806  		"RENAME TABLE test_ddl1.simple_test2 TO test_ddl1.simple_test5",                                        // ActionRenameTable
   807  		"DROP DATABASE test_ddl1",                                                                              // ActionDropSchema
   808  		"create database test_ddl2",                                                                            // ActionCreateSchema
   809  		"create table test_ddl2.simple_test1 (id bigint primary key nonclustered, c1 int not null unique key)", // ActionCreateTable
   810  		`CREATE TABLE test_ddl2.employees  (
   811  			id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   812  			fname VARCHAR(25) NOT NULL,
   813  			lname VARCHAR(25) NOT NULL,
   814  			store_id INT NOT NULL,
   815  			department_id INT NOT NULL
   816  		)
   817  
   818  		PARTITION BY RANGE(id)  (
   819  			PARTITION p0 VALUES LESS THAN (5),
   820  			PARTITION p1 VALUES LESS THAN (10),
   821  			PARTITION p2 VALUES LESS THAN (15),
   822  			PARTITION p3 VALUES LESS THAN (20)
   823  		)`, // ActionCreateTable
   824  		"ALTER TABLE test_ddl2.employees DROP PARTITION p2",                                  // ActionDropTablePartition
   825  		"ALTER TABLE test_ddl2.employees ADD PARTITION (PARTITION p4 VALUES LESS THAN (25))", // ActionAddTablePartition
   826  		"ALTER TABLE test_ddl2.employees TRUNCATE PARTITION p3",                              // ActionTruncateTablePartition
   827  		"alter table test_ddl2.employees comment='modify comment'",                           // ActionModifyTableComment
   828  		"alter table test_ddl2.simple_test1 drop primary key",                                // ActionDropPrimaryKey
   829  		"alter table test_ddl2.simple_test1 add primary key pk(id)",                          // ActionAddPrimaryKey
   830  		"ALTER TABLE test_ddl2.simple_test1 ALTER id SET DEFAULT 18",                         // ActionSetDefaultValue
   831  		"ALTER TABLE test_ddl2.simple_test1 CHARACTER SET = utf8mb4",                         // ActionModifyTableCharsetAndCollate
   832  
   833  		"ALTER TABLE test_ddl2.employees REORGANIZE PARTITION p3 INTO (PARTITION p2 VALUES LESS THAN (15), PARTITION p3 VALUES LESS THAN (20))", // ActionReorganizePartition
   834  		// "recover table test_ddl2.employees",                                                  // ActionRecoverTable this ddl can't work on mock tikv
   835  
   836  		"DROP TABLE test_ddl2.employees",
   837  		`CREATE TABLE test_ddl2.employees2  (
   838  			id INT NOT NULL,
   839  			fname VARCHAR(25) NOT NULL,
   840  			lname VARCHAR(25) NOT NULL,
   841  			store_id INT NOT NULL,
   842  			department_id INT NOT NULL
   843  		)
   844  
   845  		PARTITION BY RANGE(id)  (
   846  			PARTITION p0 VALUES LESS THAN (5),
   847  			PARTITION p1 VALUES LESS THAN (10),
   848  			PARTITION p2 VALUES LESS THAN (15),
   849  			PARTITION p3 VALUES LESS THAN (20)
   850  		)`,
   851  		"ALTER TABLE test_ddl2.employees2 CHARACTER SET = utf8mb4",
   852  		"DROP DATABASE test_ddl2",
   853  	}}
   854  
   855  	testOneGroup := func(tc []string) {
   856  		store, err := mockstore.NewMockStore()
   857  		require.Nil(t, err)
   858  		defer store.Close() //nolint:errcheck
   859  		ticonfig.UpdateGlobal(func(conf *ticonfig.Config) {
   860  			conf.AlterPrimaryKey = true
   861  		})
   862  		session.SetSchemaLease(0)
   863  		session.DisableStats4Test()
   864  		domain, err := session.BootstrapSession(store)
   865  		require.Nil(t, err)
   866  		defer domain.Close()
   867  		domain.SetStatsUpdating(true)
   868  		tk := testkit.NewTestKit(t, store)
   869  		tk.MustExec("set global tidb_enable_clustered_index = 'int_only';")
   870  		for _, ddlSQL := range tc {
   871  			tk.MustExec(ddlSQL)
   872  		}
   873  
   874  		f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   875  		require.Nil(t, err)
   876  
   877  		jobs, err := getAllHistoryDDLJob(store, f)
   878  		require.Nil(t, err)
   879  
   880  		schemaStorage, err := NewSchemaStorage(nil, 0, false, model.DefaultChangeFeedID("dummy"), util.RoleTester, f)
   881  		require.NoError(t, err)
   882  		for _, job := range jobs {
   883  			err := schemaStorage.HandleDDLJob(job)
   884  			require.NoError(t, err)
   885  		}
   886  
   887  		for _, job := range jobs {
   888  			ts := job.BinlogInfo.FinishedTS
   889  			meta := kv.GetSnapshotMeta(store, ts)
   890  			snapFromMeta, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ts, false, f)
   891  			require.Nil(t, err)
   892  			snapFromSchemaStore, err := schemaStorage.GetSnapshot(ctx, ts)
   893  			require.Nil(t, err)
   894  
   895  			s1 := snapFromMeta.DumpToString()
   896  			s2 := snapFromSchemaStore.DumpToString()
   897  			require.Equal(t, s1, s2)
   898  		}
   899  	}
   900  
   901  	for _, tc := range testCases {
   902  		testOneGroup(tc)
   903  	}
   904  }
   905  
   906  func getAllHistoryDDLJob(storage tidbkv.Storage, f filter.Filter) ([]*timodel.Job, error) {
   907  	s, err := session.CreateSession(storage)
   908  	if err != nil {
   909  		return nil, errors.Trace(err)
   910  	}
   911  
   912  	if s != nil {
   913  		defer s.Close()
   914  	}
   915  
   916  	store := domain.GetDomain(s.(sessionctx.Context)).Store()
   917  	txn, err := store.Begin()
   918  	if err != nil {
   919  		return nil, errors.Trace(err)
   920  	}
   921  	defer txn.Rollback() //nolint:errcheck
   922  	txnMeta := timeta.NewMeta(txn)
   923  
   924  	jobs, err := ddl.GetAllHistoryDDLJobs(txnMeta)
   925  	res := make([]*timodel.Job, 0)
   926  	if err != nil {
   927  		return nil, errors.Trace(err)
   928  	}
   929  	for i, job := range jobs {
   930  		ignoreSchema := f.ShouldIgnoreSchema(job.SchemaName)
   931  		ignoreTable := f.ShouldIgnoreTable(job.SchemaName, job.TableName)
   932  		if ignoreSchema || ignoreTable {
   933  			log.Info("Ignore ddl job", zap.Stringer("job", job))
   934  			continue
   935  		}
   936  		// Set State from Synced to Done.
   937  		// Because jobs are put to history queue after TiDB alter its state from
   938  		// Done to Synced.
   939  		jobs[i].State = timodel.JobStateDone
   940  		res = append(res, job)
   941  	}
   942  	return jobs, nil
   943  }
   944  
   945  // This test is used to show how the schemaStorage choose a handleKey of a table.
   946  // The handleKey is chosen by the following rules:
   947  // 1. If the table has a primary key, the handleKey is the first column of the primary key.
   948  // 2. If the table has not null unique key, the handleKey is the first column of the unique key.
   949  // 3. If the table has no primary key and no not null unique key, it has no handleKey.
   950  func TestHandleKey(t *testing.T) {
   951  	store, err := mockstore.NewMockStore()
   952  	require.Nil(t, err)
   953  	defer store.Close() //nolint:errcheck
   954  
   955  	session.SetSchemaLease(0)
   956  	session.DisableStats4Test()
   957  	domain, err := session.BootstrapSession(store)
   958  	require.Nil(t, err)
   959  	defer domain.Close()
   960  	domain.SetStatsUpdating(true)
   961  	tk := testkit.NewTestKit(t, store)
   962  	tk.MustExec("create database test2")
   963  	tk.MustExec("create table test.simple_test1 (id bigint primary key)")
   964  	tk.MustExec("create table test.simple_test2 (id bigint, age int NOT NULL, " +
   965  		"name char NOT NULL, UNIQUE KEY(age, name))")
   966  	tk.MustExec("create table test.simple_test3 (id bigint, age int)")
   967  	ver, err := store.CurrentVersion(oracle.GlobalTxnScope)
   968  	require.Nil(t, err)
   969  	meta := kv.GetSnapshotMeta(store, ver.Ver)
   970  	f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   971  	require.Nil(t, err)
   972  	snap, err := schema.NewSnapshotFromMeta(model.DefaultChangeFeedID("test"), meta, ver.Ver, false, f)
   973  	require.Nil(t, err)
   974  	tb1, ok := snap.TableByName("test", "simple_test1")
   975  	require.True(t, ok)
   976  	require.Equal(t, int64(-1), tb1.HandleIndexID) // pk is handleKey
   977  	columnID := int64(1)
   978  	flag := tb1.ColumnsFlag[columnID]
   979  	require.True(t, flag.IsHandleKey())
   980  	for _, column := range tb1.Columns {
   981  		if column.ID == columnID {
   982  			require.True(t, column.Name.O == "id")
   983  		}
   984  	}
   985  
   986  	// unique key is handleKey
   987  	tb2, ok := snap.TableByName("test", "simple_test2")
   988  	require.True(t, ok)
   989  	require.Equal(t, int64(1), tb2.HandleIndexID)
   990  	columnID = int64(2)
   991  	flag = tb2.ColumnsFlag[columnID]
   992  	require.True(t, flag.IsHandleKey())
   993  	for _, column := range tb2.Columns {
   994  		if column.ID == columnID {
   995  			require.True(t, column.Name.O == "age")
   996  		}
   997  	}
   998  
   999  	// has no handleKey
  1000  	tb3, ok := snap.TableByName("test", "simple_test3")
  1001  	require.True(t, ok)
  1002  	require.Equal(t, int64(-2), tb3.HandleIndexID)
  1003  }
  1004  
  1005  func TestGetPrimaryKey(t *testing.T) {
  1006  	helper := NewSchemaTestHelper(t)
  1007  	defer helper.Close()
  1008  	// PKISHandle is true, primary key is also the handle, since it's integer type.
  1009  	sql := `create table test.t1(a int primary key, b int)`
  1010  	event := helper.DDL2Event(sql)
  1011  
  1012  	names := event.TableInfo.GetPrimaryKeyColumnNames()
  1013  	require.Equal(t, names, []string{"a"})
  1014  
  1015  	// IsCommonHandle is true, primary key is not the handle, since it contains multiple fields.
  1016  	sql = `create table test.t2(a int, b int, c int, primary key(a, b))`
  1017  	event = helper.DDL2Event(sql)
  1018  	names = event.TableInfo.GetPrimaryKeyColumnNames()
  1019  	require.Equal(t, names, []string{"a", "b"})
  1020  
  1021  	// IsCommonHandle is true, primary key is not the handle, since it's not integer type.
  1022  	sql = `create table test.t3(a varchar(10) primary key, b int)`
  1023  	event = helper.DDL2Event(sql)
  1024  	names = event.TableInfo.GetPrimaryKeyColumnNames()
  1025  	require.Equal(t, names, []string{"a"})
  1026  }