github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/mounter_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  	"bytes"
    18  	"context"
    19  	"math"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/pingcap/log"
    25  	ticonfig "github.com/pingcap/tidb/pkg/config"
    26  	"github.com/pingcap/tidb/pkg/ddl"
    27  	"github.com/pingcap/tidb/pkg/executor"
    28  	tidbkv "github.com/pingcap/tidb/pkg/kv"
    29  	"github.com/pingcap/tidb/pkg/meta/autoid"
    30  	"github.com/pingcap/tidb/pkg/parser"
    31  	"github.com/pingcap/tidb/pkg/parser/ast"
    32  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    33  	"github.com/pingcap/tidb/pkg/parser/mysql"
    34  	"github.com/pingcap/tidb/pkg/session"
    35  	"github.com/pingcap/tidb/pkg/store/mockstore"
    36  	"github.com/pingcap/tidb/pkg/testkit"
    37  	"github.com/pingcap/tidb/pkg/types"
    38  	"github.com/pingcap/tidb/pkg/util/mock"
    39  	"github.com/pingcap/tiflow/cdc/model"
    40  	"github.com/pingcap/tiflow/pkg/config"
    41  	cerror "github.com/pingcap/tiflow/pkg/errors"
    42  	"github.com/pingcap/tiflow/pkg/filter"
    43  	"github.com/pingcap/tiflow/pkg/integrity"
    44  	"github.com/pingcap/tiflow/pkg/sink/codec/avro"
    45  	codecCommon "github.com/pingcap/tiflow/pkg/sink/codec/common"
    46  	"github.com/pingcap/tiflow/pkg/spanz"
    47  	"github.com/pingcap/tiflow/pkg/sqlmodel"
    48  	"github.com/pingcap/tiflow/pkg/util"
    49  	"github.com/stretchr/testify/require"
    50  	"github.com/tikv/client-go/v2/oracle"
    51  	"go.uber.org/zap"
    52  )
    53  
    54  var dummyChangeFeedID = model.DefaultChangeFeedID("dummy_changefeed")
    55  
    56  func TestMounterDisableOldValue(t *testing.T) {
    57  	testCases := []struct {
    58  		tableName      string
    59  		createTableDDL string
    60  		// [] for rows, []interface{} for columns.
    61  		values [][]interface{}
    62  		// [] for table partition if there is any,
    63  		// []int for approximateBytes of rows.
    64  		putApproximateBytes [][]int
    65  		delApproximateBytes [][]int
    66  	}{{
    67  		tableName:           "simple",
    68  		createTableDDL:      "create table simple(id int primary key)",
    69  		values:              [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    70  		putApproximateBytes: [][]int{{346, 346, 346, 346, 346}},
    71  		delApproximateBytes: [][]int{{346, 346, 346, 346, 346}},
    72  	}, {
    73  		tableName:           "no_pk",
    74  		createTableDDL:      "create table no_pk(id int not null unique key)",
    75  		values:              [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    76  		putApproximateBytes: [][]int{{345, 345, 345, 345, 345}},
    77  		delApproximateBytes: [][]int{{217, 217, 217, 217, 217}},
    78  	}, {
    79  		tableName:           "many_index",
    80  		createTableDDL:      "create table many_index(id int not null unique key, c1 int unique key, c2 int, INDEX (c2))",
    81  		values:              [][]interface{}{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}},
    82  		putApproximateBytes: [][]int{{638, 638, 638, 638, 638}},
    83  		delApproximateBytes: [][]int{{254, 254, 254, 254, 254}},
    84  	}, {
    85  		tableName:           "default_value",
    86  		createTableDDL:      "create table default_value(id int primary key, c1 int, c2 int not null default 5, c3 varchar(20), c4 varchar(20) not null default '666')",
    87  		values:              [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    88  		putApproximateBytes: [][]int{{676, 676, 676, 676, 676}},
    89  		delApproximateBytes: [][]int{{353, 353, 353, 353, 353}},
    90  	}, {
    91  		tableName: "partition_table",
    92  		createTableDDL: `CREATE TABLE partition_table  (
    93  				id INT NOT NULL AUTO_INCREMENT UNIQUE KEY,
    94  				fname VARCHAR(25) NOT NULL,
    95  				lname VARCHAR(25) NOT NULL,
    96  				store_id INT NOT NULL,
    97  				department_id INT NOT NULL,
    98  				INDEX (department_id)
    99  			)
   100  
   101  			PARTITION BY RANGE(id)  (
   102  				PARTITION p0 VALUES LESS THAN (5),
   103  				PARTITION p1 VALUES LESS THAN (10),
   104  				PARTITION p2 VALUES LESS THAN (15),
   105  				PARTITION p3 VALUES LESS THAN (20)
   106  			)`,
   107  		values: [][]interface{}{
   108  			{1, "aa", "bb", 12, 12},
   109  			{6, "aac", "bab", 51, 51},
   110  			{11, "aad", "bsb", 71, 61},
   111  			{18, "aae", "bbf", 21, 14},
   112  			{15, "afa", "bbc", 11, 12},
   113  		},
   114  		putApproximateBytes: [][]int{{775}, {777}, {777}, {777, 777}},
   115  		delApproximateBytes: [][]int{{227}, {227}, {227}, {227, 227}},
   116  	}, {
   117  		tableName: "tp_int",
   118  		createTableDDL: `create table tp_int
   119  			(
   120  				id          int auto_increment,
   121  				c_tinyint   tinyint   null,
   122  				c_smallint  smallint  null,
   123  				c_mediumint mediumint null,
   124  				c_int       int       null,
   125  				c_bigint    bigint    null,
   126  				constraint pk
   127  					primary key (id)
   128  			);`,
   129  		values: [][]interface{}{
   130  			{1, 1, 2, 3, 4, 5},
   131  			{2},
   132  			{3, 3, 4, 5, 6, 7},
   133  			{4, 127, 32767, 8388607, 2147483647, 9223372036854775807},
   134  			{5, -128, -32768, -8388608, -2147483648, -9223372036854775808},
   135  		},
   136  		putApproximateBytes: [][]int{{986, 626, 986, 986, 986}},
   137  		delApproximateBytes: [][]int{{346, 346, 346, 346, 346}},
   138  	}, {
   139  		tableName: "tp_text",
   140  		createTableDDL: `create table tp_text
   141  		(
   142  			id           int auto_increment,
   143  			c_tinytext   tinytext      null,
   144  			c_text       text          null,
   145  			c_mediumtext mediumtext    null,
   146  			c_longtext   longtext      null,
   147  			c_varchar    varchar(16)   null,
   148  			c_char       char(16)      null,
   149  			c_tinyblob   tinyblob      null,
   150  			c_blob       blob          null,
   151  			c_mediumblob mediumblob    null,
   152  			c_longblob   longblob      null,
   153  			c_binary     binary(16)    null,
   154  			c_varbinary  varbinary(16) null,
   155  			constraint pk
   156  			primary key (id)
   157  		);`,
   158  		values: [][]interface{}{
   159  			{1},
   160  			{
   161  				2, "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A",
   162  				"89504E470D0A1A0A",
   163  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   164  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   165  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   166  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   167  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   168  				string([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}),
   169  			},
   170  			{
   171  				3, "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free",
   172  				"bug free", "bug free", "bug free", "bug free",
   173  			},
   174  			{4, "", "", "", "", "", "", "", "", "", "", "", ""},
   175  			{5, "你好", "我好", "大家好", "道路", "千万条", "安全", "第一条", "行车", "不规范", "亲人", "两行泪", "!"},
   176  			{6, "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "☺️", "😊", "😇", "🙂"},
   177  		},
   178  		putApproximateBytes: [][]int{{1019, 1459, 1411, 1323, 1398, 1369}},
   179  		delApproximateBytes: [][]int{{347, 347, 347, 347, 347, 347}},
   180  	}, {
   181  		tableName: "tp_time",
   182  		createTableDDL: `create table tp_time
   183  		(
   184  			id          int auto_increment,
   185  			c_date      date      null,
   186  			c_datetime  datetime  null,
   187  			c_timestamp timestamp null,
   188  			c_time      time      null,
   189  			c_year      year      null,
   190  			constraint pk
   191  			primary key (id)
   192  		);`,
   193  		values: [][]interface{}{
   194  			{1},
   195  			{2, "2020-02-20", "2020-02-20 02:20:20", "2020-02-20 02:20:20", "02:20:20", "2020"},
   196  		},
   197  		putApproximateBytes: [][]int{{627, 819}},
   198  		delApproximateBytes: [][]int{{347, 347}},
   199  	}, {
   200  		tableName: "tp_real",
   201  		createTableDDL: `create table tp_real
   202  	(
   203  		id        int auto_increment,
   204  		c_float   float   null,
   205  		c_double  double  null,
   206  		c_decimal decimal null,
   207  		constraint pk
   208  		primary key (id)
   209  	);`,
   210  		values: [][]interface{}{
   211  			{1},
   212  			{2, "2020.0202", "2020.0303", "2020.0404"},
   213  		},
   214  		putApproximateBytes: [][]int{{563, 551}},
   215  		delApproximateBytes: [][]int{{347, 347}},
   216  	}, {
   217  		tableName: "tp_other",
   218  		createTableDDL: `create table tp_other
   219  		(
   220  			id     int auto_increment,
   221  			c_enum enum ('a','b','c') null,
   222  			c_set  set ('a','b','c')  null,
   223  			c_bit  bit(64)            null,
   224  			c_json json               null,
   225  			constraint pk
   226  			primary key (id)
   227  		);`,
   228  		values: [][]interface{}{
   229  			{1},
   230  			{2, "a", "a,c", 888, `{"aa":"bb"}`},
   231  		},
   232  		putApproximateBytes: [][]int{{636, 624}},
   233  		delApproximateBytes: [][]int{{348, 348}},
   234  	}, {
   235  		tableName:      "clustered_index1",
   236  		createTableDDL: "CREATE TABLE clustered_index1 (id VARCHAR(255) PRIMARY KEY, data INT);",
   237  		values: [][]interface{}{
   238  			{"hhh"},
   239  			{"你好😘", 666},
   240  			{"世界🤪", 888},
   241  		},
   242  		putApproximateBytes: [][]int{{383, 446, 446}},
   243  		delApproximateBytes: [][]int{{311, 318, 318}},
   244  	}, {
   245  		tableName:      "clustered_index2",
   246  		createTableDDL: "CREATE TABLE clustered_index2 (id VARCHAR(255), data INT, ddaa date, PRIMARY KEY (id, data, ddaa), UNIQUE KEY (id, data, ddaa));",
   247  		values: [][]interface{}{
   248  			{"你好😘", 666, "2020-11-20"},
   249  			{"世界🤪", 888, "2020-05-12"},
   250  		},
   251  		putApproximateBytes: [][]int{{592, 592}},
   252  		delApproximateBytes: [][]int{{592, 592}},
   253  	}}
   254  	for _, tc := range testCases {
   255  		testMounterDisableOldValue(t, tc)
   256  	}
   257  }
   258  
   259  func testMounterDisableOldValue(t *testing.T, tc struct {
   260  	tableName           string
   261  	createTableDDL      string
   262  	values              [][]interface{}
   263  	putApproximateBytes [][]int
   264  	delApproximateBytes [][]int
   265  },
   266  ) {
   267  	store, err := mockstore.NewMockStore()
   268  	require.Nil(t, err)
   269  	defer store.Close() //nolint:errcheck
   270  	ticonfig.UpdateGlobal(func(conf *ticonfig.Config) {
   271  		// we can update the tidb config here
   272  	})
   273  	session.SetSchemaLease(0)
   274  	session.DisableStats4Test()
   275  	domain, err := session.BootstrapSession(store)
   276  	require.Nil(t, err)
   277  	defer domain.Close()
   278  	domain.SetStatsUpdating(true)
   279  	tk := testkit.NewTestKit(t, store)
   280  	tk.MustExec("set @@tidb_enable_clustered_index=1;")
   281  	tk.MustExec("use test;")
   282  
   283  	tk.MustExec(tc.createTableDDL)
   284  
   285  	f, err := filter.NewFilter(config.GetDefaultReplicaConfig(), "")
   286  	require.Nil(t, err)
   287  	jobs, err := getAllHistoryDDLJob(store, f)
   288  	require.Nil(t, err)
   289  
   290  	scheamStorage, err := NewSchemaStorage(nil, 0, false, dummyChangeFeedID, util.RoleTester, f)
   291  	require.Nil(t, err)
   292  	for _, job := range jobs {
   293  		err := scheamStorage.HandleDDLJob(job)
   294  		require.Nil(t, err)
   295  	}
   296  	tableInfo, ok := scheamStorage.GetLastSnapshot().TableByName("test", tc.tableName)
   297  	require.True(t, ok)
   298  	if tableInfo.IsCommonHandle {
   299  		// we can check this log to make sure if the clustered-index is enabled
   300  		log.Info("this table is enable the clustered index", zap.String("tableName", tableInfo.Name.L))
   301  	}
   302  
   303  	for _, params := range tc.values {
   304  
   305  		insertSQL := prepareInsertSQL(t, tableInfo, len(params))
   306  		tk.MustExec(insertSQL, params...)
   307  	}
   308  
   309  	ver, err := store.CurrentVersion(oracle.GlobalTxnScope)
   310  	require.Nil(t, err)
   311  	scheamStorage.AdvanceResolvedTs(ver.Ver)
   312  	config := config.GetDefaultReplicaConfig()
   313  	filter, err := filter.NewFilter(config, "")
   314  	require.Nil(t, err)
   315  	mounter := NewMounter(scheamStorage,
   316  		model.DefaultChangeFeedID("c1"), time.UTC, filter, config.Integrity).(*mounter)
   317  	mounter.tz = time.Local
   318  	ctx := context.Background()
   319  
   320  	// [TODO] check size and readd rowBytes
   321  	mountAndCheckRowInTable := func(tableID int64, _ []int, f func(key []byte, value []byte) *model.RawKVEntry) int {
   322  		var rows int
   323  		walkTableSpanInStore(t, store, tableID, func(key []byte, value []byte) {
   324  			rawKV := f(key, value)
   325  			row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV)
   326  			require.Nil(t, err)
   327  			if row == nil {
   328  				return
   329  			}
   330  			rows++
   331  			require.Equal(t, row.TableInfo.GetTableName(), tc.tableName)
   332  			require.Equal(t, row.TableInfo.GetSchemaName(), "test")
   333  			// [TODO] check size and reopen this check
   334  			// require.Equal(t, rowBytes[rows-1], row.ApproximateBytes(), row)
   335  			t.Log("ApproximateBytes", tc.tableName, rows-1, row.ApproximateBytes())
   336  			// TODO: test column flag, column type and index columns
   337  			if len(row.Columns) != 0 {
   338  				checkSQL, params := prepareCheckSQL(t, tc.tableName, row.GetColumns())
   339  				result := tk.MustQuery(checkSQL, params...)
   340  				result.Check([][]interface{}{{"1"}})
   341  			}
   342  			if len(row.PreColumns) != 0 {
   343  				checkSQL, params := prepareCheckSQL(t, tc.tableName, row.GetPreColumns())
   344  				result := tk.MustQuery(checkSQL, params...)
   345  				result.Check([][]interface{}{{"1"}})
   346  			}
   347  		})
   348  		return rows
   349  	}
   350  	mountAndCheckRow := func(rowsBytes [][]int, f func(key []byte, value []byte) *model.RawKVEntry) int {
   351  		partitionInfo := tableInfo.GetPartitionInfo()
   352  		if partitionInfo == nil {
   353  			return mountAndCheckRowInTable(tableInfo.ID, rowsBytes[0], f)
   354  		}
   355  		var rows int
   356  		for i, p := range partitionInfo.Definitions {
   357  			rows += mountAndCheckRowInTable(p.ID, rowsBytes[i], f)
   358  		}
   359  		return rows
   360  	}
   361  
   362  	rows := mountAndCheckRow(tc.putApproximateBytes, func(key []byte, value []byte) *model.RawKVEntry {
   363  		return &model.RawKVEntry{
   364  			OpType:  model.OpTypePut,
   365  			Key:     key,
   366  			Value:   value,
   367  			StartTs: ver.Ver - 1,
   368  			CRTs:    ver.Ver,
   369  		}
   370  	})
   371  	require.Equal(t, rows, len(tc.values))
   372  
   373  	rows = mountAndCheckRow(tc.delApproximateBytes, func(key []byte, value []byte) *model.RawKVEntry {
   374  		return &model.RawKVEntry{
   375  			OpType:  model.OpTypeDelete,
   376  			Key:     key,
   377  			Value:   nil, // delete event doesn't include a value when old-value is disabled
   378  			StartTs: ver.Ver - 1,
   379  			CRTs:    ver.Ver,
   380  		}
   381  	})
   382  	require.Equal(t, rows, len(tc.values))
   383  }
   384  
   385  func prepareInsertSQL(t *testing.T, tableInfo *model.TableInfo, columnLens int) string {
   386  	var sb strings.Builder
   387  	_, err := sb.WriteString("INSERT INTO " + tableInfo.Name.O + "(")
   388  	require.Nil(t, err)
   389  	for i := 0; i < columnLens; i++ {
   390  		col := tableInfo.Columns[i]
   391  		if i != 0 {
   392  			_, err = sb.WriteString(", ")
   393  			require.Nil(t, err)
   394  		}
   395  		_, err = sb.WriteString(col.Name.O)
   396  		require.Nil(t, err)
   397  	}
   398  	_, err = sb.WriteString(") VALUES (")
   399  	require.Nil(t, err)
   400  	for i := 0; i < columnLens; i++ {
   401  		if i != 0 {
   402  			_, err = sb.WriteString(", ")
   403  			require.Nil(t, err)
   404  		}
   405  		_, err = sb.WriteString("?")
   406  		require.Nil(t, err)
   407  	}
   408  	_, err = sb.WriteString(")")
   409  	require.Nil(t, err)
   410  	return sb.String()
   411  }
   412  
   413  func prepareCheckSQL(t *testing.T, tableName string, cols []*model.Column) (string, []interface{}) {
   414  	var sb strings.Builder
   415  	_, err := sb.WriteString("SELECT count(1) FROM " + tableName + " WHERE ")
   416  	require.Nil(t, err)
   417  	params := make([]interface{}, 0, len(cols))
   418  	for i, col := range cols {
   419  		// Since float type has precision problem, so skip it to avoid compare float number.
   420  		if col == nil || col.Type == mysql.TypeFloat {
   421  			continue
   422  		}
   423  		if i != 0 {
   424  			_, err = sb.WriteString(" AND ")
   425  			require.Nil(t, err)
   426  		}
   427  		if col.Value == nil {
   428  			_, err = sb.WriteString(col.Name + " IS NULL")
   429  			require.Nil(t, err)
   430  			continue
   431  		}
   432  		// convert types for tk.MustQuery
   433  		if bytes, ok := col.Value.([]byte); ok {
   434  			col.Value = string(bytes)
   435  		}
   436  		params = append(params, col.Value)
   437  		if col.Type == mysql.TypeJSON {
   438  			_, err = sb.WriteString(col.Name + " = CAST(? AS JSON)")
   439  		} else {
   440  			_, err = sb.WriteString(col.Name + " = ?")
   441  		}
   442  		require.Nil(t, err)
   443  	}
   444  	return sb.String(), params
   445  }
   446  
   447  func walkTableSpanInStore(t *testing.T, store tidbkv.Storage, tableID int64, f func(key []byte, value []byte)) {
   448  	txn, err := store.Begin()
   449  	require.Nil(t, err)
   450  	defer txn.Rollback() //nolint:errcheck
   451  	startKey, endKey := spanz.GetTableRange(tableID)
   452  	kvIter, err := txn.Iter(startKey, endKey)
   453  	require.Nil(t, err)
   454  	defer kvIter.Close()
   455  	for kvIter.Valid() {
   456  		f(kvIter.Key(), kvIter.Value())
   457  		err = kvIter.Next()
   458  		require.Nil(t, err)
   459  	}
   460  }
   461  
   462  func getLastKeyValueInStore(t *testing.T, store tidbkv.Storage, tableID int64) (key, value []byte) {
   463  	txn, err := store.Begin()
   464  	require.NoError(t, err)
   465  	defer txn.Rollback() //nolint:errcheck
   466  	startKey, endKey := spanz.GetTableRange(tableID)
   467  	kvIter, err := txn.Iter(startKey, endKey)
   468  	require.NoError(t, err)
   469  	defer kvIter.Close()
   470  	for kvIter.Valid() {
   471  		key = kvIter.Key()
   472  		value = kvIter.Value()
   473  		err = kvIter.Next()
   474  		require.NoError(t, err)
   475  	}
   476  	return key, value
   477  }
   478  
   479  // We use OriginDefaultValue instead of DefaultValue in the ut, pls ref to
   480  // https://github.com/pingcap/tiflow/issues/4048
   481  // Ref: https://github.com/pingcap/tidb/blob/d2c352980a43bb593db81fd1db996f47af596d91/table/column.go#L489
   482  func TestGetDefaultZeroValue(t *testing.T) {
   483  	// Check following MySQL type, ref to:
   484  	// https://github.com/pingcap/tidb/blob/master/parser/mysql/type.go
   485  
   486  	// mysql flag null
   487  	ftNull := types.NewFieldType(mysql.TypeUnspecified)
   488  
   489  	// mysql.TypeTiny + notnull
   490  	ftTinyIntNotNull := types.NewFieldType(mysql.TypeTiny)
   491  	ftTinyIntNotNull.AddFlag(mysql.NotNullFlag)
   492  
   493  	// mysql.TypeTiny + notnull +  unsigned
   494  	ftTinyIntNotNullUnSigned := types.NewFieldType(mysql.TypeTiny)
   495  	ftTinyIntNotNullUnSigned.SetFlag(mysql.NotNullFlag)
   496  	ftTinyIntNotNullUnSigned.AddFlag(mysql.UnsignedFlag)
   497  
   498  	// mysql.TypeTiny + null
   499  	ftTinyIntNull := types.NewFieldType(mysql.TypeTiny)
   500  
   501  	// mysql.TypeShort + notnull
   502  	ftShortNotNull := types.NewFieldType(mysql.TypeShort)
   503  	ftShortNotNull.SetFlag(mysql.NotNullFlag)
   504  
   505  	// mysql.TypeLong + notnull
   506  	ftLongNotNull := types.NewFieldType(mysql.TypeLong)
   507  	ftLongNotNull.SetFlag(mysql.NotNullFlag)
   508  
   509  	// mysql.TypeLonglong + notnull
   510  	ftLongLongNotNull := types.NewFieldType(mysql.TypeLonglong)
   511  	ftLongLongNotNull.SetFlag(mysql.NotNullFlag)
   512  
   513  	// mysql.TypeInt24 + notnull
   514  	ftInt24NotNull := types.NewFieldType(mysql.TypeInt24)
   515  	ftInt24NotNull.SetFlag(mysql.NotNullFlag)
   516  
   517  	// mysql.TypeFloat + notnull
   518  	ftTypeFloatNotNull := types.NewFieldType(mysql.TypeFloat)
   519  	ftTypeFloatNotNull.SetFlag(mysql.NotNullFlag)
   520  
   521  	// mysql.TypeFloat + notnull + unsigned
   522  	ftTypeFloatNotNullUnSigned := types.NewFieldType(mysql.TypeFloat)
   523  	ftTypeFloatNotNullUnSigned.SetFlag(mysql.NotNullFlag | mysql.UnsignedFlag)
   524  
   525  	// mysql.TypeFloat + null
   526  	ftTypeFloatNull := types.NewFieldType(mysql.TypeFloat)
   527  
   528  	// mysql.TypeDouble + notnull
   529  	ftTypeDoubleNotNull := types.NewFieldType(mysql.TypeDouble)
   530  	ftTypeDoubleNotNull.SetFlag(mysql.NotNullFlag)
   531  
   532  	// mysql.TypeNewDecimal + notnull
   533  	ftTypeNewDecimalNull := types.NewFieldType(mysql.TypeNewDecimal)
   534  	ftTypeNewDecimalNull.SetFlen(5)
   535  	ftTypeNewDecimalNull.SetDecimal(2)
   536  
   537  	// mysql.TypeNewDecimal + notnull
   538  	ftTypeNewDecimalNotNull := types.NewFieldType(mysql.TypeNewDecimal)
   539  	ftTypeNewDecimalNotNull.SetFlag(mysql.NotNullFlag)
   540  	ftTypeNewDecimalNotNull.SetFlen(5)
   541  	ftTypeNewDecimalNotNull.SetDecimal(2)
   542  
   543  	// mysql.TypeNull
   544  	ftTypeNull := types.NewFieldType(mysql.TypeNull)
   545  
   546  	// mysql.TypeTimestamp + notnull
   547  	ftTypeTimestampNotNull := types.NewFieldType(mysql.TypeTimestamp)
   548  	ftTypeTimestampNotNull.SetFlag(mysql.NotNullFlag)
   549  
   550  	// mysql.TypeTimestamp + notnull
   551  	ftTypeTimestampNull := types.NewFieldType(mysql.TypeTimestamp)
   552  
   553  	// mysql.TypeDate + notnull
   554  	ftTypeDateNotNull := types.NewFieldType(mysql.TypeDate)
   555  	ftTypeDateNotNull.SetFlag(mysql.NotNullFlag)
   556  
   557  	// mysql.TypeDuration + notnull
   558  	ftTypeDurationNotNull := types.NewFieldType(mysql.TypeDuration)
   559  	ftTypeDurationNotNull.SetFlag(mysql.NotNullFlag)
   560  
   561  	// mysql.TypeDatetime + notnull
   562  	ftTypeDatetimeNotNull := types.NewFieldType(mysql.TypeDatetime)
   563  	ftTypeDatetimeNotNull.SetFlag(mysql.NotNullFlag)
   564  
   565  	// mysql.TypeYear + notnull
   566  	ftTypeYearNotNull := types.NewFieldType(mysql.TypeYear)
   567  	ftTypeYearNotNull.SetFlag(mysql.NotNullFlag)
   568  
   569  	// mysql.TypeNewDate + notnull
   570  	ftTypeNewDateNotNull := types.NewFieldType(mysql.TypeNewDate)
   571  	ftTypeNewDateNotNull.SetFlag(mysql.NotNullFlag)
   572  
   573  	// mysql.TypeVarchar + notnull
   574  	ftTypeVarcharNotNull := types.NewFieldType(mysql.TypeVarchar)
   575  	ftTypeVarcharNotNull.SetFlag(mysql.NotNullFlag)
   576  
   577  	// mysql.TypeTinyBlob + notnull
   578  	ftTypeTinyBlobNotNull := types.NewFieldType(mysql.TypeTinyBlob)
   579  	ftTypeTinyBlobNotNull.SetFlag(mysql.NotNullFlag)
   580  
   581  	// mysql.TypeMediumBlob + notnull
   582  	ftTypeMediumBlobNotNull := types.NewFieldType(mysql.TypeMediumBlob)
   583  	ftTypeMediumBlobNotNull.SetFlag(mysql.NotNullFlag)
   584  
   585  	// mysql.TypeLongBlob + notnull
   586  	ftTypeLongBlobNotNull := types.NewFieldType(mysql.TypeLongBlob)
   587  	ftTypeLongBlobNotNull.SetFlag(mysql.NotNullFlag)
   588  
   589  	// mysql.TypeBlob + notnull
   590  	ftTypeBlobNotNull := types.NewFieldType(mysql.TypeBlob)
   591  	ftTypeBlobNotNull.SetFlag(mysql.NotNullFlag)
   592  
   593  	// mysql.TypeVarString + notnull
   594  	ftTypeVarStringNotNull := types.NewFieldType(mysql.TypeVarString)
   595  	ftTypeVarStringNotNull.SetFlag(mysql.NotNullFlag)
   596  
   597  	// mysql.TypeString + notnull
   598  	ftTypeStringNotNull := types.NewFieldType(mysql.TypeString)
   599  	ftTypeStringNotNull.SetFlag(mysql.NotNullFlag)
   600  
   601  	// mysql.TypeBit + notnull
   602  	ftTypeBitNotNull := types.NewFieldType(mysql.TypeBit)
   603  	ftTypeBitNotNull.SetFlag(mysql.NotNullFlag)
   604  
   605  	// mysql.TypeJSON + notnull
   606  	ftTypeJSONNotNull := types.NewFieldType(mysql.TypeJSON)
   607  	ftTypeJSONNotNull.SetFlag(mysql.NotNullFlag)
   608  
   609  	// mysql.TypeEnum + notnull + nodefault
   610  	ftTypeEnumNotNull := types.NewFieldType(mysql.TypeEnum)
   611  	ftTypeEnumNotNull.SetFlag(mysql.NotNullFlag)
   612  	ftTypeEnumNotNull.SetElems([]string{"e0", "e1"})
   613  
   614  	// mysql.TypeEnum + null
   615  	ftTypeEnumNull := types.NewFieldType(mysql.TypeEnum)
   616  
   617  	// mysql.TypeSet + notnull
   618  	ftTypeSetNotNull := types.NewFieldType(mysql.TypeSet)
   619  	ftTypeSetNotNull.SetFlag(mysql.NotNullFlag)
   620  	ftTypeSetNotNull.SetElems([]string{"1", "e"})
   621  
   622  	// mysql.TypeGeometry + notnull
   623  	ftTypeGeometryNotNull := types.NewFieldType(mysql.TypeGeometry)
   624  	ftTypeGeometryNotNull.SetFlag(mysql.NotNullFlag)
   625  
   626  	testCases := []struct {
   627  		Name    string
   628  		ColInfo timodel.ColumnInfo
   629  		Res     interface{}
   630  	}{
   631  		{
   632  			Name:    "mysql flag null",
   633  			ColInfo: timodel.ColumnInfo{FieldType: *ftNull},
   634  			Res:     nil,
   635  		},
   636  		{
   637  			Name:    "mysql.TypeTiny + notnull + nodefault",
   638  			ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNotNull.Clone()},
   639  			Res:     int64(0),
   640  		},
   641  		{
   642  			Name: "mysql.TypeTiny + notnull + default",
   643  			ColInfo: timodel.ColumnInfo{
   644  				OriginDefaultValue: "-128",
   645  				FieldType:          *ftTinyIntNotNull,
   646  			},
   647  			Res: int64(-128),
   648  		},
   649  		{
   650  			Name:    "mysql.TypeTiny + notnull + default + unsigned",
   651  			ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNotNullUnSigned},
   652  			Res:     uint64(0),
   653  		},
   654  		{
   655  			Name:    "mysql.TypeTiny + notnull + unsigned",
   656  			ColInfo: timodel.ColumnInfo{OriginDefaultValue: "127", FieldType: *ftTinyIntNotNullUnSigned},
   657  			Res:     uint64(127),
   658  		},
   659  		{
   660  			Name: "mysql.TypeTiny + null + default",
   661  			ColInfo: timodel.ColumnInfo{
   662  				OriginDefaultValue: "-128",
   663  				FieldType:          *ftTinyIntNull,
   664  			},
   665  			Res: int64(-128),
   666  		},
   667  		{
   668  			Name:    "mysql.TypeTiny + null + nodefault",
   669  			ColInfo: timodel.ColumnInfo{FieldType: *ftTinyIntNull},
   670  			Res:     nil,
   671  		},
   672  		{
   673  			Name:    "mysql.TypeShort, others testCases same as tiny",
   674  			ColInfo: timodel.ColumnInfo{FieldType: *ftShortNotNull},
   675  			Res:     int64(0),
   676  		},
   677  		{
   678  			Name:    "mysql.TypeLong, others testCases same as tiny",
   679  			ColInfo: timodel.ColumnInfo{FieldType: *ftLongNotNull},
   680  			Res:     int64(0),
   681  		},
   682  		{
   683  			Name:    "mysql.TypeLonglong, others testCases same as tiny",
   684  			ColInfo: timodel.ColumnInfo{FieldType: *ftLongLongNotNull},
   685  			Res:     int64(0),
   686  		},
   687  		{
   688  			Name:    "mysql.TypeInt24, others testCases same as tiny",
   689  			ColInfo: timodel.ColumnInfo{FieldType: *ftInt24NotNull},
   690  			Res:     int64(0),
   691  		},
   692  		{
   693  			Name:    "mysql.TypeFloat + notnull + nodefault",
   694  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeFloatNotNull},
   695  			Res:     float32(0),
   696  		},
   697  		{
   698  			Name: "mysql.TypeFloat + notnull + default",
   699  			ColInfo: timodel.ColumnInfo{
   700  				OriginDefaultValue: float32(-3.1415),
   701  				FieldType:          *ftTypeFloatNotNull,
   702  			},
   703  			Res: float32(-3.1415),
   704  		},
   705  		{
   706  			Name: "mysql.TypeFloat + notnull + default + unsigned",
   707  			ColInfo: timodel.ColumnInfo{
   708  				OriginDefaultValue: float32(3.1415),
   709  				FieldType:          *ftTypeFloatNotNullUnSigned,
   710  			},
   711  			Res: float32(3.1415),
   712  		},
   713  		{
   714  			Name: "mysql.TypeFloat + notnull + unsigned",
   715  			ColInfo: timodel.ColumnInfo{
   716  				FieldType: *ftTypeFloatNotNullUnSigned,
   717  			},
   718  			Res: float32(0),
   719  		},
   720  		{
   721  			Name: "mysql.TypeFloat + null + default",
   722  			ColInfo: timodel.ColumnInfo{
   723  				OriginDefaultValue: float32(-3.1415),
   724  				FieldType:          *ftTypeFloatNull,
   725  			},
   726  			Res: float32(-3.1415),
   727  		},
   728  		{
   729  			Name: "mysql.TypeFloat + null + nodefault",
   730  			ColInfo: timodel.ColumnInfo{
   731  				FieldType: *ftTypeFloatNull,
   732  			},
   733  			Res: nil,
   734  		},
   735  		{
   736  			Name:    "mysql.TypeDouble, other testCases same as float",
   737  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDoubleNotNull},
   738  			Res:     float64(0),
   739  		},
   740  		{
   741  			Name:    "mysql.TypeNewDecimal + notnull + nodefault",
   742  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDecimalNotNull},
   743  			Res:     "0", // related with Flen and Decimal
   744  		},
   745  		{
   746  			Name:    "mysql.TypeNewDecimal + null + nodefault",
   747  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDecimalNull},
   748  			Res:     nil,
   749  		},
   750  		{
   751  			Name:    "mysql.TypeNull",
   752  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNull},
   753  			Res:     nil,
   754  		},
   755  		{
   756  			Name:    "mysql.TypeTimestamp + notnull + nodefault",
   757  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeTimestampNotNull},
   758  			Res:     "0000-00-00 00:00:00",
   759  		},
   760  		{
   761  			Name:    "mysql.TypeDate, other testCases same as TypeTimestamp",
   762  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDateNotNull},
   763  			Res:     "0000-00-00",
   764  		},
   765  		{
   766  			Name:    "mysql.TypeDuration, other testCases same as TypeTimestamp",
   767  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDurationNotNull},
   768  			Res:     "00:00:00",
   769  		},
   770  		{
   771  			Name:    "mysql.TypeDatetime, other testCases same as TypeTimestamp",
   772  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeDatetimeNotNull},
   773  			Res:     "0000-00-00 00:00:00",
   774  		},
   775  		{
   776  			Name:    "mysql.TypeYear + notnull + nodefault",
   777  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeYearNotNull},
   778  			Res:     int64(0),
   779  		},
   780  		{
   781  			Name: "mysql.TypeYear + notnull + default",
   782  			ColInfo: timodel.ColumnInfo{
   783  				OriginDefaultValue: "2021",
   784  				FieldType:          *ftTypeYearNotNull,
   785  			},
   786  			Res: int64(2021),
   787  		},
   788  		{
   789  			Name:    "mysql.TypeNewDate",
   790  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeNewDateNotNull},
   791  			Res:     nil, // [TODO] seems not support by TiDB, need check
   792  		},
   793  		{
   794  			Name:    "mysql.TypeVarchar + notnull + nodefault",
   795  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeVarcharNotNull},
   796  			Res:     []byte{},
   797  		},
   798  		{
   799  			Name: "mysql.TypeVarchar + notnull + default",
   800  			ColInfo: timodel.ColumnInfo{
   801  				OriginDefaultValue: "e0",
   802  				FieldType:          *ftTypeVarcharNotNull,
   803  			},
   804  			Res: []byte("e0"),
   805  		},
   806  		{
   807  			Name:    "mysql.TypeTinyBlob",
   808  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeTinyBlobNotNull},
   809  			Res:     []byte{},
   810  		},
   811  		{
   812  			Name:    "mysql.TypeMediumBlob",
   813  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeMediumBlobNotNull},
   814  			Res:     []byte{},
   815  		},
   816  		{
   817  			Name:    "mysql.TypeLongBlob",
   818  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeLongBlobNotNull},
   819  			Res:     []byte{},
   820  		},
   821  		{
   822  			Name:    "mysql.TypeBlob",
   823  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeBlobNotNull},
   824  			Res:     []byte{},
   825  		},
   826  		{
   827  			Name:    "mysql.TypeVarString",
   828  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeVarStringNotNull},
   829  			Res:     []byte{},
   830  		},
   831  		{
   832  			Name:    "mysql.TypeString",
   833  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeStringNotNull},
   834  			Res:     []byte{},
   835  		},
   836  		{
   837  			Name:    "mysql.TypeBit",
   838  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeBitNotNull},
   839  			Res:     uint64(0),
   840  		},
   841  		// BLOB, TEXT, GEOMETRY or JSON column can't have a default value
   842  		{
   843  			Name:    "mysql.TypeJSON",
   844  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeJSONNotNull},
   845  			Res:     "null",
   846  		},
   847  		{
   848  			Name:    "mysql.TypeEnum + notnull + nodefault",
   849  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeEnumNotNull},
   850  			// TypeEnum value will be a string and then translate to []byte
   851  			// NotNull && no default will choose first element
   852  			Res: uint64(1),
   853  		},
   854  		{
   855  			Name: "mysql.TypeEnum + null",
   856  			ColInfo: timodel.ColumnInfo{
   857  				FieldType: *ftTypeEnumNull,
   858  			},
   859  			Res: nil,
   860  		},
   861  		{
   862  			Name:    "mysql.TypeSet + notnull",
   863  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeSetNotNull},
   864  			Res:     uint64(0),
   865  		},
   866  		{
   867  			Name:    "mysql.TypeGeometry",
   868  			ColInfo: timodel.ColumnInfo{FieldType: *ftTypeGeometryNotNull},
   869  			Res:     nil, // not support yet
   870  		},
   871  	}
   872  
   873  	tz, err := util.GetTimezone(config.GetGlobalServerConfig().TZ)
   874  	require.NoError(t, err)
   875  	for _, tc := range testCases {
   876  		_, val, _, _, _ := getDefaultOrZeroValue(&tc.ColInfo, tz)
   877  		require.Equal(t, tc.Res, val, tc.Name)
   878  	}
   879  
   880  	colInfo := timodel.ColumnInfo{
   881  		OriginDefaultValue: "-3.14", // no float
   882  		FieldType:          *ftTypeNewDecimalNotNull,
   883  	}
   884  	_, val, _, _, _ := getDefaultOrZeroValue(&colInfo, tz)
   885  	decimal := new(types.MyDecimal)
   886  	err = decimal.FromString([]byte("-3.14"))
   887  	require.NoError(t, err)
   888  	require.Equal(t, decimal.String(), val, "mysql.TypeNewDecimal + notnull + default")
   889  
   890  	colInfo = timodel.ColumnInfo{
   891  		OriginDefaultValue: "2020-11-19 12:12:12",
   892  		FieldType:          *ftTypeTimestampNotNull,
   893  	}
   894  	_, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz)
   895  	expected, err := types.ParseTimeFromFloatString(
   896  		types.DefaultStmtNoWarningContext,
   897  		"2020-11-19 20:12:12", colInfo.FieldType.GetType(), colInfo.FieldType.GetDecimal())
   898  	require.NoError(t, err)
   899  	require.Equal(t, expected.String(), val, "mysql.TypeTimestamp + notnull + default")
   900  
   901  	colInfo = timodel.ColumnInfo{
   902  		OriginDefaultValue: "2020-11-19 12:12:12",
   903  		FieldType:          *ftTypeTimestampNull,
   904  	}
   905  	_, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz)
   906  	expected, err = types.ParseTimeFromFloatString(
   907  		types.DefaultStmtNoWarningContext,
   908  		"2020-11-19 20:12:12", colInfo.FieldType.GetType(), colInfo.FieldType.GetDecimal())
   909  	require.NoError(t, err)
   910  	require.Equal(t, expected.String(), val, "mysql.TypeTimestamp + null + default")
   911  
   912  	colInfo = timodel.ColumnInfo{
   913  		OriginDefaultValue: "e1",
   914  		FieldType:          *ftTypeEnumNotNull,
   915  	}
   916  	_, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz)
   917  	expectedEnum, err := types.ParseEnumName(colInfo.FieldType.GetElems(), "e1", colInfo.FieldType.GetCollate())
   918  	require.NoError(t, err)
   919  	require.Equal(t, expectedEnum.Value, val, "mysql.TypeEnum + notnull + default")
   920  
   921  	colInfo = timodel.ColumnInfo{
   922  		OriginDefaultValue: "1,e",
   923  		FieldType:          *ftTypeSetNotNull,
   924  	}
   925  	_, val, _, _, _ = getDefaultOrZeroValue(&colInfo, tz)
   926  	expectedSet, err := types.ParseSetName(colInfo.FieldType.GetElems(), "1,e", colInfo.FieldType.GetCollate())
   927  	require.NoError(t, err)
   928  	require.Equal(t, expectedSet.Value, val, "mysql.TypeSet + notnull + default")
   929  }
   930  
   931  func TestE2ERowLevelChecksum(t *testing.T) {
   932  	helper := NewSchemaTestHelper(t)
   933  	defer helper.Close()
   934  
   935  	tk := helper.Tk()
   936  	// upstream TiDB enable checksum functionality
   937  	tk.MustExec("set global tidb_enable_row_level_checksum = 1")
   938  	helper.Tk().MustExec("use test")
   939  
   940  	// changefeed enable checksum functionality
   941  	replicaConfig := config.GetDefaultReplicaConfig()
   942  	replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness
   943  	filter, err := filter.NewFilter(replicaConfig, "")
   944  	require.NoError(t, err)
   945  
   946  	ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope)
   947  	require.NoError(t, err)
   948  
   949  	changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row")
   950  	schemaStorage, err := NewSchemaStorage(helper.Storage(),
   951  		ver.Ver, false, changefeed, util.RoleTester, filter)
   952  	require.NoError(t, err)
   953  	require.NotNil(t, schemaStorage)
   954  
   955  	createTableSQL := `create table t (
   956     id          int primary key auto_increment,
   957  
   958     c_tinyint   tinyint   null,
   959     c_smallint  smallint  null,
   960     c_mediumint mediumint null,
   961     c_int       int       null,
   962     c_bigint    bigint    null,
   963  
   964     c_unsigned_tinyint   tinyint   unsigned null,
   965     c_unsigned_smallint  smallint  unsigned null,
   966     c_unsigned_mediumint mediumint unsigned null,
   967     c_unsigned_int       int       unsigned null,
   968     c_unsigned_bigint    bigint    unsigned null,
   969  
   970     c_float   float   null,
   971     c_double  double  null,
   972     c_decimal decimal null,
   973     c_decimal_2 decimal(10, 4) null,
   974  
   975     c_unsigned_float     float unsigned   null,
   976     c_unsigned_double    double unsigned  null,
   977     c_unsigned_decimal   decimal unsigned null,
   978     c_unsigned_decimal_2 decimal(10, 4) unsigned null,
   979  
   980     c_date      date      null,
   981     c_datetime  datetime  null,
   982     c_timestamp timestamp null,
   983     c_time      time      null,
   984     c_year      year      null,
   985  
   986     c_tinytext   tinytext      null,
   987     c_text       text          null,
   988     c_mediumtext mediumtext    null,
   989     c_longtext   longtext      null,
   990  
   991     c_tinyblob   tinyblob      null,
   992     c_blob       blob          null,
   993     c_mediumblob mediumblob    null,
   994     c_longblob   longblob      null,
   995  
   996     c_char       char(16)      null,
   997     c_varchar    varchar(16)   null,
   998     c_binary     binary(16)    null,
   999     c_varbinary  varbinary(16) null,
  1000  
  1001     c_enum enum ('a','b','c') null,
  1002     c_set  set ('a','b','c')  null,
  1003     c_bit  bit(64)            null,
  1004     c_json json               null,
  1005  
  1006  -- gbk dmls
  1007     name varchar(128) CHARACTER SET gbk,
  1008     country char(32) CHARACTER SET gbk,
  1009     city varchar(64),
  1010     description text CHARACTER SET gbk,
  1011     image tinyblob
  1012  );`
  1013  	job := helper.DDL2Job(createTableSQL)
  1014  	err = schemaStorage.HandleDDLJob(job)
  1015  	require.NoError(t, err)
  1016  
  1017  	ts := schemaStorage.GetLastSnapshot().CurrentTs()
  1018  	schemaStorage.AdvanceResolvedTs(ver.Ver)
  1019  
  1020  	mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, replicaConfig.Integrity).(*mounter)
  1021  
  1022  	ctx, cancel := context.WithCancel(context.Background())
  1023  	defer cancel()
  1024  
  1025  	tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "t")
  1026  	require.True(t, ok)
  1027  
  1028  	tk.Session().GetSessionVars().EnableRowLevelChecksum = true
  1029  
  1030  	insertDataSQL := `insert into t values (
  1031       2,
  1032       1, 2, 3, 4, 5,
  1033       1, 2, 3, 4, 5,
  1034       2020.0202, 2020.0303, 2020.0404, 2021.1208,
  1035       3.1415, 2.7182, 8000, 179394.233,
  1036       '2020-02-20', '2020-02-20 02:20:20', '2020-02-20 02:20:20', '02:20:20', '2020',
  1037       '89504E470D0A1A0A', '89504E470D0A1A0A', '89504E470D0A1A0A', '89504E470D0A1A0A',
  1038       x'89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A',
  1039       '89504E470D0A1A0A', '89504E470D0A1A0A', x'89504E470D0A1A0A', x'89504E470D0A1A0A',
  1040       'b', 'b,c', b'1000001', '{
  1041  "key1": "value1",
  1042  "key2": "value2",
  1043  "key3": "123"
  1044  }',
  1045       '测试', "中国", "上海", "你好,世界", 0xC4E3BAC3CAC0BDE7
  1046  );`
  1047  	tk.MustExec(insertDataSQL)
  1048  
  1049  	key, value := getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID)
  1050  	rawKV := &model.RawKVEntry{
  1051  		OpType:  model.OpTypePut,
  1052  		Key:     key,
  1053  		Value:   value,
  1054  		StartTs: ts - 1,
  1055  		CRTs:    ts + 1,
  1056  	}
  1057  	row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1058  	require.NoError(t, err)
  1059  	require.NotNil(t, row)
  1060  	require.NotNil(t, row.Checksum)
  1061  
  1062  	expected, ok := mounter.decoder.GetChecksum()
  1063  	require.True(t, ok)
  1064  	require.Equal(t, expected, row.Checksum.Current)
  1065  	require.False(t, row.Checksum.Corrupted)
  1066  
  1067  	// avro encoder enable checksum functionality.
  1068  	codecConfig := codecCommon.NewConfig(config.ProtocolAvro)
  1069  	codecConfig.EnableTiDBExtension = true
  1070  	codecConfig.EnableRowChecksum = true
  1071  	codecConfig.AvroDecimalHandlingMode = "string"
  1072  	codecConfig.AvroBigintUnsignedHandlingMode = "string"
  1073  
  1074  	avroEncoder, err := avro.SetupEncoderAndSchemaRegistry4Testing(ctx, codecConfig)
  1075  	defer avro.TeardownEncoderAndSchemaRegistry4Testing()
  1076  	require.NoError(t, err)
  1077  
  1078  	topic := "test.t"
  1079  
  1080  	err = avroEncoder.AppendRowChangedEvent(ctx, topic, row, func() {})
  1081  	require.NoError(t, err)
  1082  	msg := avroEncoder.Build()
  1083  	require.Len(t, msg, 1)
  1084  
  1085  	schemaM, err := avro.NewConfluentSchemaManager(
  1086  		ctx, "http://127.0.0.1:8081", nil)
  1087  	require.NoError(t, err)
  1088  
  1089  	// decoder enable checksum functionality.
  1090  	decoder := avro.NewDecoder(codecConfig, schemaM, topic)
  1091  	err = decoder.AddKeyValue(msg[0].Key, msg[0].Value)
  1092  	require.NoError(t, err)
  1093  
  1094  	messageType, hasNext, err := decoder.HasNext()
  1095  	require.NoError(t, err)
  1096  	require.True(t, hasNext)
  1097  	require.Equal(t, model.MessageTypeRow, messageType)
  1098  
  1099  	row, err = decoder.NextRowChangedEvent()
  1100  	// no error, checksum verification passed.
  1101  	require.NoError(t, err)
  1102  }
  1103  
  1104  func TestTimezoneDefaultValue(t *testing.T) {
  1105  	helper := NewSchemaTestHelper(t)
  1106  	defer helper.Close()
  1107  
  1108  	_ = helper.DDL2Event(`create table test.t(a int primary key)`)
  1109  	insertEvent := helper.DML2Event(`insert into test.t values (1)`, "test", "t")
  1110  	require.NotNil(t, insertEvent)
  1111  
  1112  	tableInfo, ok := helper.schemaStorage.GetLastSnapshot().TableByName("test", "t")
  1113  	require.True(t, ok)
  1114  
  1115  	key, oldValue := helper.getLastKeyValue(tableInfo.ID)
  1116  
  1117  	_ = helper.DDL2Event(`alter table test.t add column b timestamp default '2023-02-09 13:00:00'`)
  1118  	ts := helper.schemaStorage.GetLastSnapshot().CurrentTs()
  1119  	rawKV := &model.RawKVEntry{
  1120  		OpType:   model.OpTypePut,
  1121  		Key:      key,
  1122  		OldValue: oldValue,
  1123  		StartTs:  ts - 1,
  1124  		CRTs:     ts + 1,
  1125  	}
  1126  	polymorphicEvent := model.NewPolymorphicEvent(rawKV)
  1127  	err := helper.mounter.DecodeEvent(context.Background(), polymorphicEvent)
  1128  	require.NoError(t, err)
  1129  
  1130  	event := polymorphicEvent.Row
  1131  	require.NotNil(t, event)
  1132  	require.Equal(t, "2023-02-09 13:00:00", event.PreColumns[1].Value.(string))
  1133  }
  1134  
  1135  func TestVerifyChecksumTime(t *testing.T) {
  1136  	replicaConfig := config.GetDefaultReplicaConfig()
  1137  	replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness
  1138  	replicaConfig.Integrity.CorruptionHandleLevel = integrity.CorruptionHandleLevelError
  1139  
  1140  	helper := NewSchemaTestHelperWithReplicaConfig(t, replicaConfig)
  1141  	defer helper.Close()
  1142  
  1143  	helper.Tk().MustExec("set global tidb_enable_row_level_checksum = 1")
  1144  	helper.Tk().MustExec("use test")
  1145  
  1146  	helper.Tk().MustExec("set global time_zone = '-5:00'")
  1147  	_ = helper.DDL2Event(`CREATE table TBL2 (a int primary key, b TIMESTAMP)`)
  1148  	event := helper.DML2Event(`INSERT INTO TBL2 VALUES (1, '2023-02-09 13:00:00')`, "test", "TBL2")
  1149  	require.NotNil(t, event)
  1150  
  1151  	_ = helper.DDL2Event("create table t (a timestamp primary key, b int)")
  1152  	event = helper.DML2Event("insert into t values ('2023-02-09 13:00:00', 1)", "test", "t")
  1153  	require.NotNil(t, event)
  1154  }
  1155  
  1156  func TestDecodeRowEnableChecksum(t *testing.T) {
  1157  	helper := NewSchemaTestHelper(t)
  1158  	defer helper.Close()
  1159  
  1160  	tk := helper.Tk()
  1161  
  1162  	tk.MustExec("set global tidb_enable_row_level_checksum = 1")
  1163  	helper.Tk().MustExec("use test")
  1164  
  1165  	replicaConfig := config.GetDefaultReplicaConfig()
  1166  	replicaConfig.Integrity.IntegrityCheckLevel = integrity.CheckLevelCorrectness
  1167  	filter, err := filter.NewFilter(replicaConfig, "")
  1168  	require.NoError(t, err)
  1169  
  1170  	ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope)
  1171  	require.NoError(t, err)
  1172  
  1173  	changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row")
  1174  	schemaStorage, err := NewSchemaStorage(helper.Storage(),
  1175  		ver.Ver, false, changefeed, util.RoleTester, filter)
  1176  	require.NoError(t, err)
  1177  	require.NotNil(t, schemaStorage)
  1178  
  1179  	createTableDDL := "create table t (id int primary key, a int)"
  1180  	job := helper.DDL2Job(createTableDDL)
  1181  	err = schemaStorage.HandleDDLJob(job)
  1182  	require.NoError(t, err)
  1183  
  1184  	ts := schemaStorage.GetLastSnapshot().CurrentTs()
  1185  	schemaStorage.AdvanceResolvedTs(ver.Ver)
  1186  
  1187  	mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, replicaConfig.Integrity).(*mounter)
  1188  
  1189  	ctx := context.Background()
  1190  
  1191  	tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "t")
  1192  	require.True(t, ok)
  1193  
  1194  	// row without checksum
  1195  	tk.Session().GetSessionVars().EnableRowLevelChecksum = false
  1196  	tk.MustExec("insert into t values (1, 10)")
  1197  
  1198  	key, value := getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID)
  1199  	rawKV := &model.RawKVEntry{
  1200  		OpType:  model.OpTypePut,
  1201  		Key:     key,
  1202  		Value:   value,
  1203  		StartTs: ts - 1,
  1204  		CRTs:    ts + 1,
  1205  	}
  1206  
  1207  	row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1208  	require.NoError(t, err)
  1209  	require.NotNil(t, row)
  1210  	// the upstream tidb does not enable checksum, so the checksum is nil
  1211  	require.Nil(t, row.Checksum)
  1212  
  1213  	// 	row with one checksum
  1214  	tk.Session().GetSessionVars().EnableRowLevelChecksum = true
  1215  	tk.MustExec("insert into t values (2, 20)")
  1216  
  1217  	key, value = getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID)
  1218  	rawKV = &model.RawKVEntry{
  1219  		OpType:  model.OpTypePut,
  1220  		Key:     key,
  1221  		Value:   value,
  1222  		StartTs: ts - 1,
  1223  		CRTs:    ts + 1,
  1224  	}
  1225  	row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1226  	require.NoError(t, err)
  1227  	require.NotNil(t, row)
  1228  	require.NotNil(t, row.Checksum)
  1229  
  1230  	expected, ok := mounter.decoder.GetChecksum()
  1231  	require.True(t, ok)
  1232  	require.Equal(t, expected, row.Checksum.Current)
  1233  	require.False(t, row.Checksum.Corrupted)
  1234  
  1235  	// row with 2 checksum
  1236  	tk.MustExec("insert into t values (3, 30)")
  1237  	job = helper.DDL2Job("alter table t change column a a varchar(10)")
  1238  	err = schemaStorage.HandleDDLJob(job)
  1239  	require.NoError(t, err)
  1240  
  1241  	key, value = getLastKeyValueInStore(t, helper.Storage(), tableInfo.ID)
  1242  	rawKV = &model.RawKVEntry{
  1243  		OpType:  model.OpTypePut,
  1244  		Key:     key,
  1245  		Value:   value,
  1246  		StartTs: ts - 1,
  1247  		CRTs:    ts + 1,
  1248  	}
  1249  	row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1250  	require.NoError(t, err)
  1251  	require.NotNil(t, row)
  1252  	require.NotNil(t, row.Checksum)
  1253  
  1254  	first, ok := mounter.decoder.GetChecksum()
  1255  	require.True(t, ok)
  1256  
  1257  	extra, ok := mounter.decoder.GetExtraChecksum()
  1258  	require.True(t, ok)
  1259  
  1260  	if row.Checksum.Current != first {
  1261  		require.Equal(t, extra, row.Checksum.Current)
  1262  	} else {
  1263  		require.Equal(t, first, row.Checksum.Current)
  1264  	}
  1265  	require.False(t, row.Checksum.Corrupted)
  1266  
  1267  	// hack the table info to make the checksum corrupted
  1268  	tableInfo.Columns[0].FieldType = *types.NewFieldType(mysql.TypeVarchar)
  1269  
  1270  	// corrupt-handle-level default to warn, so no error, but the checksum is corrupted
  1271  	row, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1272  	require.NoError(t, err)
  1273  	require.NotNil(t, row.Checksum)
  1274  	require.True(t, row.Checksum.Corrupted)
  1275  
  1276  	mounter.integrity.CorruptionHandleLevel = integrity.CorruptionHandleLevelError
  1277  	_, err = mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1278  	require.Error(t, err)
  1279  	require.ErrorIs(t, err, cerror.ErrCorruptedDataMutation)
  1280  
  1281  	job = helper.DDL2Job("drop table t")
  1282  	err = schemaStorage.HandleDDLJob(job)
  1283  	require.NoError(t, err)
  1284  }
  1285  
  1286  func TestDecodeRow(t *testing.T) {
  1287  	helper := NewSchemaTestHelper(t)
  1288  	defer helper.Close()
  1289  
  1290  	helper.Tk().MustExec("set @@tidb_enable_clustered_index=1;")
  1291  	helper.Tk().MustExec("use test;")
  1292  
  1293  	changefeed := model.DefaultChangeFeedID("changefeed-test-decode-row")
  1294  
  1295  	ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope)
  1296  	require.NoError(t, err)
  1297  
  1298  	cfg := config.GetDefaultReplicaConfig()
  1299  
  1300  	filter, err := filter.NewFilter(cfg, "")
  1301  	require.NoError(t, err)
  1302  
  1303  	schemaStorage, err := NewSchemaStorage(helper.Storage(),
  1304  		ver.Ver, false, changefeed, util.RoleTester, filter)
  1305  	require.NoError(t, err)
  1306  
  1307  	// apply ddl to schemaStorage
  1308  	ddl := "create table test.student(id int primary key, name char(50), age int, gender char(10))"
  1309  	job := helper.DDL2Job(ddl)
  1310  	err = schemaStorage.HandleDDLJob(job)
  1311  	require.NoError(t, err)
  1312  
  1313  	ts := schemaStorage.GetLastSnapshot().CurrentTs()
  1314  
  1315  	schemaStorage.AdvanceResolvedTs(ver.Ver)
  1316  
  1317  	mounter := NewMounter(schemaStorage, changefeed, time.Local, filter, cfg.Integrity).(*mounter)
  1318  
  1319  	helper.Tk().MustExec(`insert into student values(1, "dongmen", 20, "male")`)
  1320  	helper.Tk().MustExec(`update student set age = 27 where id = 1`)
  1321  
  1322  	ctx := context.Background()
  1323  	decodeAndCheckRowInTable := func(tableID int64, f func(key []byte, value []byte) *model.RawKVEntry) {
  1324  		walkTableSpanInStore(t, helper.Storage(), tableID, func(key []byte, value []byte) {
  1325  			rawKV := f(key, value)
  1326  
  1327  			row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV)
  1328  			require.NoError(t, err)
  1329  			require.NotNil(t, row)
  1330  
  1331  			if row.Columns != nil {
  1332  				require.NotNil(t, mounter.decoder)
  1333  			}
  1334  
  1335  			if row.PreColumns != nil {
  1336  				require.NotNil(t, mounter.preDecoder)
  1337  			}
  1338  		})
  1339  	}
  1340  
  1341  	toRawKV := func(key []byte, value []byte) *model.RawKVEntry {
  1342  		return &model.RawKVEntry{
  1343  			OpType:  model.OpTypePut,
  1344  			Key:     key,
  1345  			Value:   value,
  1346  			StartTs: ts - 1,
  1347  			CRTs:    ts + 1,
  1348  		}
  1349  	}
  1350  
  1351  	tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName("test", "student")
  1352  	require.True(t, ok)
  1353  
  1354  	decodeAndCheckRowInTable(tableInfo.ID, toRawKV)
  1355  	decodeAndCheckRowInTable(tableInfo.ID, toRawKV)
  1356  
  1357  	job = helper.DDL2Job("drop table student")
  1358  	err = schemaStorage.HandleDDLJob(job)
  1359  	require.NoError(t, err)
  1360  }
  1361  
  1362  // TestDecodeEventIgnoreRow tests a PolymorphicEvent.Row is nil
  1363  // if this event should be filter out by filter.
  1364  func TestDecodeEventIgnoreRow(t *testing.T) {
  1365  	helper := NewSchemaTestHelper(t)
  1366  	defer helper.Close()
  1367  	helper.Tk().MustExec("use test;")
  1368  
  1369  	ddls := []string{
  1370  		"create table test.student(id int primary key, name char(50), age int, gender char(10))",
  1371  		"create table test.computer(id int primary key, brand char(50), price int)",
  1372  		"create table test.poet(id int primary key, name char(50), works char(100))",
  1373  	}
  1374  
  1375  	cfID := model.DefaultChangeFeedID("changefeed-test-ignore-event")
  1376  
  1377  	cfg := config.GetDefaultReplicaConfig()
  1378  	cfg.Filter.Rules = []string{"test.student", "test.computer"}
  1379  	f, err := filter.NewFilter(cfg, "")
  1380  	require.Nil(t, err)
  1381  	ver, err := helper.Storage().CurrentVersion(oracle.GlobalTxnScope)
  1382  	require.Nil(t, err)
  1383  
  1384  	schemaStorage, err := NewSchemaStorage(helper.Storage(),
  1385  		ver.Ver, false, cfID, util.RoleTester, f)
  1386  	require.Nil(t, err)
  1387  	// apply ddl to schemaStorage
  1388  	for _, ddl := range ddls {
  1389  		job := helper.DDL2Job(ddl)
  1390  		err = schemaStorage.HandleDDLJob(job)
  1391  		require.Nil(t, err)
  1392  	}
  1393  
  1394  	ts := schemaStorage.GetLastSnapshot().CurrentTs()
  1395  	schemaStorage.AdvanceResolvedTs(ver.Ver)
  1396  	mounter := NewMounter(schemaStorage, cfID, time.Local, f, cfg.Integrity).(*mounter)
  1397  
  1398  	type testCase struct {
  1399  		schema  string
  1400  		table   string
  1401  		columns []interface{}
  1402  		ignored bool
  1403  	}
  1404  
  1405  	testCases := []testCase{
  1406  		{
  1407  			schema:  "test",
  1408  			table:   "student",
  1409  			columns: []interface{}{1, "dongmen", 20, "male"},
  1410  			ignored: false,
  1411  		},
  1412  		{
  1413  			schema:  "test",
  1414  			table:   "computer",
  1415  			columns: []interface{}{1, "apple", 19999},
  1416  			ignored: false,
  1417  		},
  1418  		// This case should be ignored by its table name.
  1419  		{
  1420  			schema:  "test",
  1421  			table:   "poet",
  1422  			columns: []interface{}{1, "李白", "静夜思"},
  1423  			ignored: true,
  1424  		},
  1425  	}
  1426  
  1427  	ignoredTables := make([]string, 0)
  1428  	tables := make([]string, 0)
  1429  	for _, tc := range testCases {
  1430  		tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName(tc.schema, tc.table)
  1431  		require.True(t, ok)
  1432  		// TODO: add other dml event type
  1433  		insertSQL := prepareInsertSQL(t, tableInfo, len(tc.columns))
  1434  		if tc.ignored {
  1435  			ignoredTables = append(ignoredTables, tc.table)
  1436  		} else {
  1437  			tables = append(tables, tc.table)
  1438  		}
  1439  		helper.tk.MustExec(insertSQL, tc.columns...)
  1440  	}
  1441  	ctx := context.Background()
  1442  
  1443  	decodeAndCheckRowInTable := func(tableID int64, f func(key []byte, value []byte) *model.RawKVEntry) int {
  1444  		var rows int
  1445  		walkTableSpanInStore(t, helper.Storage(), tableID, func(key []byte, value []byte) {
  1446  			rawKV := f(key, value)
  1447  			pEvent := model.NewPolymorphicEvent(rawKV)
  1448  			err := mounter.DecodeEvent(ctx, pEvent)
  1449  			require.Nil(t, err)
  1450  			if pEvent.Row == nil {
  1451  				return
  1452  			}
  1453  			row := pEvent.Row
  1454  			rows++
  1455  			require.Equal(t, row.TableInfo.GetSchemaName(), "test")
  1456  			// Now we only allow filter dml event by table, so we only check row's table.
  1457  			require.NotContains(t, ignoredTables, row.TableInfo.GetTableName())
  1458  			require.Contains(t, tables, row.TableInfo.GetTableName())
  1459  		})
  1460  		return rows
  1461  	}
  1462  
  1463  	toRawKV := func(key []byte, value []byte) *model.RawKVEntry {
  1464  		return &model.RawKVEntry{
  1465  			OpType:  model.OpTypePut,
  1466  			Key:     key,
  1467  			Value:   value,
  1468  			StartTs: ts - 1,
  1469  			CRTs:    ts + 1,
  1470  		}
  1471  	}
  1472  
  1473  	for _, tc := range testCases {
  1474  		tableInfo, ok := schemaStorage.GetLastSnapshot().TableByName(tc.schema, tc.table)
  1475  		require.True(t, ok)
  1476  		decodeAndCheckRowInTable(tableInfo.ID, toRawKV)
  1477  	}
  1478  }
  1479  
  1480  func TestBuildTableInfo(t *testing.T) {
  1481  	cases := []struct {
  1482  		origin              string
  1483  		recovered           string
  1484  		recoveredWithNilCol string
  1485  	}{
  1486  		{
  1487  			"CREATE TABLE t1 (c INT PRIMARY KEY)",
  1488  			"CREATE TABLE `t1` (\n" +
  1489  				"  `c` int(0) NOT NULL,\n" +
  1490  				"  PRIMARY KEY (`c`(0)) /*T![clustered_index] CLUSTERED */\n" +
  1491  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1492  			"CREATE TABLE `t1` (\n" +
  1493  				"  `c` int(0) NOT NULL,\n" +
  1494  				"  PRIMARY KEY (`c`(0)) /*T![clustered_index] CLUSTERED */\n" +
  1495  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1496  		},
  1497  		{
  1498  			"CREATE TABLE t1 (" +
  1499  				" c INT UNSIGNED," +
  1500  				" c2 VARCHAR(10) NOT NULL," +
  1501  				" c3 BIT(10) NOT NULL," +
  1502  				" UNIQUE KEY (c2, c3)" +
  1503  				")",
  1504  			// CDC discards field length.
  1505  			"CREATE TABLE `t1` (\n" +
  1506  				"  `c` int(0) unsigned DEFAULT NULL,\n" +
  1507  				"  `c2` varchar(0) NOT NULL,\n" +
  1508  				"  `c3` bit(0) NOT NULL,\n" +
  1509  				"  UNIQUE KEY `idx_0` (`c2`(0),`c3`(0))\n" +
  1510  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1511  			"CREATE TABLE `t1` (\n" +
  1512  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1513  				"  `c2` varchar(0) NOT NULL,\n" +
  1514  				"  `c3` bit(0) NOT NULL,\n" +
  1515  				"  UNIQUE KEY `idx_0` (`c2`(0),`c3`(0))\n" +
  1516  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1517  		},
  1518  		{
  1519  			"CREATE TABLE t1 (" +
  1520  				" c INT UNSIGNED," +
  1521  				" gen INT AS (c+1) VIRTUAL," +
  1522  				" c2 VARCHAR(10) NOT NULL," +
  1523  				" gen2 INT AS (c+2) STORED," +
  1524  				" c3 BIT(10) NOT NULL," +
  1525  				" PRIMARY KEY (c, c2)" +
  1526  				")",
  1527  			// CDC discards virtual generated column, and generating expression of stored generated column.
  1528  			"CREATE TABLE `t1` (\n" +
  1529  				"  `c` int(0) unsigned NOT NULL,\n" +
  1530  				"  `c2` varchar(0) NOT NULL,\n" +
  1531  				"  `gen2` int(0) GENERATED ALWAYS AS (pass_generated_check) STORED,\n" +
  1532  				"  `c3` bit(0) NOT NULL,\n" +
  1533  				"  PRIMARY KEY (`c`(0),`c2`(0)) /*T![clustered_index] CLUSTERED */\n" +
  1534  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1535  			"CREATE TABLE `t1` (\n" +
  1536  				"  `c` int(0) unsigned NOT NULL,\n" +
  1537  				"  `c2` varchar(0) NOT NULL,\n" +
  1538  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1539  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1540  				"  PRIMARY KEY (`c`(0),`c2`(0)) /*T![clustered_index] CLUSTERED */\n" +
  1541  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1542  		},
  1543  		{
  1544  			"CREATE TABLE `t1` (" +
  1545  				"  `a` int(11) NOT NULL," +
  1546  				"  `b` int(11) DEFAULT NULL," +
  1547  				"  `c` int(11) DEFAULT NULL," +
  1548  				"  PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */," +
  1549  				"  UNIQUE KEY `b` (`b`)" +
  1550  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1551  			"CREATE TABLE `t1` (\n" +
  1552  				"  `a` int(0) NOT NULL,\n" +
  1553  				"  `b` int(0) DEFAULT NULL,\n" +
  1554  				"  `c` int(0) DEFAULT NULL,\n" +
  1555  				"  PRIMARY KEY (`a`(0)) /*T![clustered_index] CLUSTERED */,\n" +
  1556  				"  UNIQUE KEY `idx_1` (`b`(0))\n" +
  1557  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1558  			"CREATE TABLE `t1` (\n" +
  1559  				"  `a` int(0) NOT NULL,\n" +
  1560  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1561  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1562  				"  PRIMARY KEY (`a`(0)) /*T![clustered_index] CLUSTERED */\n" +
  1563  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1564  		},
  1565  		{ // This case is to check the primary key is correctly identified by BuildTiDBTableInfo
  1566  			"CREATE TABLE your_table (" +
  1567  				" id INT NOT NULL," +
  1568  				" name VARCHAR(50) NOT NULL," +
  1569  				" email VARCHAR(100) NOT NULL," +
  1570  				" age INT NOT NULL ," +
  1571  				" address VARCHAR(200) NOT NULL," +
  1572  				" PRIMARY KEY (id, name)," +
  1573  				" UNIQUE INDEX idx_unique_1 (id, email, age)," +
  1574  				" UNIQUE INDEX idx_unique_2 (name, email, address)" +
  1575  				" );",
  1576  			"CREATE TABLE `your_table` (\n" +
  1577  				"  `id` int(0) NOT NULL,\n" +
  1578  				"  `name` varchar(0) NOT NULL,\n" +
  1579  				"  `email` varchar(0) NOT NULL,\n" +
  1580  				"  `age` int(0) NOT NULL,\n" +
  1581  				"  `address` varchar(0) NOT NULL,\n" +
  1582  				"  PRIMARY KEY (`id`(0),`name`(0)) /*T![clustered_index] CLUSTERED */,\n" +
  1583  				"  UNIQUE KEY `idx_1` (`id`(0),`email`(0),`age`(0)),\n" +
  1584  				"  UNIQUE KEY `idx_2` (`name`(0),`email`(0),`address`(0))\n" +
  1585  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1586  			"CREATE TABLE `your_table` (\n" +
  1587  				"  `id` int(0) NOT NULL,\n" +
  1588  				"  `name` varchar(0) NOT NULL,\n" +
  1589  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1590  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1591  				"  `omitted` unspecified GENERATED ALWAYS AS (pass_generated_check) VIRTUAL,\n" +
  1592  				"  PRIMARY KEY (`id`(0),`name`(0)) /*T![clustered_index] CLUSTERED */,\n" +
  1593  				"  UNIQUE KEY `idx_1` (`id`(0),`omitted`(0),`omitted`(0)),\n" +
  1594  				"  UNIQUE KEY `idx_2` (`name`(0),`omitted`(0),`omitted`(0))\n" +
  1595  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1596  		},
  1597  	}
  1598  	tz, err := util.GetTimezone(config.GetGlobalServerConfig().TZ)
  1599  	require.NoError(t, err)
  1600  	p := parser.New()
  1601  	for i, c := range cases {
  1602  		stmt, err := p.ParseOneStmt(c.origin, "", "")
  1603  		require.NoError(t, err)
  1604  		originTI, err := ddl.BuildTableInfoFromAST(stmt.(*ast.CreateTableStmt))
  1605  		require.NoError(t, err)
  1606  		cdcTableInfo := model.WrapTableInfo(0, "test", 0, originTI)
  1607  		colDatas, _, _, err := datum2Column(cdcTableInfo, map[int64]types.Datum{}, tz)
  1608  		require.NoError(t, err)
  1609  		e := model.RowChangedEvent{
  1610  			TableInfo: cdcTableInfo,
  1611  			Columns:   colDatas,
  1612  		}
  1613  		cols := e.GetColumns()
  1614  		recoveredTI := model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset)
  1615  		handle := sqlmodel.GetWhereHandle(recoveredTI, recoveredTI)
  1616  		require.NotNil(t, handle.UniqueNotNullIdx)
  1617  		require.Equal(t, c.recovered, showCreateTable(t, recoveredTI))
  1618  		// make sure BuildTiDBTableInfo indentify the correct primary key
  1619  		if i == 5 {
  1620  			inexes := recoveredTI.Indices
  1621  			primaryCount := 0
  1622  			for i := range inexes {
  1623  				if inexes[i].Primary {
  1624  					primaryCount++
  1625  				}
  1626  			}
  1627  			require.Equal(t, 1, primaryCount)
  1628  			require.Equal(t, 2, len(handle.UniqueNotNullIdx.Columns))
  1629  		}
  1630  		// mimic the columns are set to nil when old value feature is disabled
  1631  		for i := range cols {
  1632  			if !cols[i].Flag.IsHandleKey() {
  1633  				cols[i] = nil
  1634  			}
  1635  		}
  1636  		recoveredTI = model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset)
  1637  		handle = sqlmodel.GetWhereHandle(recoveredTI, recoveredTI)
  1638  		require.NotNil(t, handle.UniqueNotNullIdx)
  1639  		require.Equal(t, c.recoveredWithNilCol, showCreateTable(t, recoveredTI))
  1640  	}
  1641  }
  1642  
  1643  var tiCtx = mock.NewContext()
  1644  
  1645  func showCreateTable(t *testing.T, ti *timodel.TableInfo) string {
  1646  	result := bytes.NewBuffer(make([]byte, 0, 512))
  1647  	err := executor.ConstructResultOfShowCreateTable(tiCtx, ti, autoid.Allocators{}, result)
  1648  	require.NoError(t, err)
  1649  	return result.String()
  1650  }
  1651  
  1652  func TestNewDMRowChange(t *testing.T) {
  1653  	cases := []struct {
  1654  		origin    string
  1655  		recovered string
  1656  	}{
  1657  		{
  1658  			"CREATE TABLE t1 (id INT," +
  1659  				" a1 INT NOT NULL," +
  1660  				" a3 INT NOT NULL," +
  1661  				" UNIQUE KEY dex1(a1, a3));",
  1662  			"CREATE TABLE `t1` (\n" +
  1663  				"  `id` int(0) DEFAULT NULL,\n" +
  1664  				"  `a1` int(0) NOT NULL,\n" +
  1665  				"  `a3` int(0) NOT NULL,\n" +
  1666  				"  UNIQUE KEY `idx_0` (`a1`(0),`a3`(0))\n" +
  1667  				") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin",
  1668  		},
  1669  	}
  1670  	p := parser.New()
  1671  	for _, c := range cases {
  1672  		stmt, err := p.ParseOneStmt(c.origin, "", "")
  1673  		require.NoError(t, err)
  1674  		originTI, err := ddl.BuildTableInfoFromAST(stmt.(*ast.CreateTableStmt))
  1675  		require.NoError(t, err)
  1676  		cdcTableInfo := model.WrapTableInfo(0, "test", 0, originTI)
  1677  		cols := []*model.Column{
  1678  			{
  1679  				Name: "id", Type: 3, Charset: "binary", Flag: 65, Value: 1, Default: nil,
  1680  			},
  1681  			{
  1682  				Name: "a1", Type: 3, Charset: "binary", Flag: 51, Value: 1, Default: nil,
  1683  			},
  1684  			{
  1685  				Name: "a3", Type: 3, Charset: "binary", Flag: 51, Value: 2, Default: nil,
  1686  			},
  1687  		}
  1688  		recoveredTI := model.BuildTiDBTableInfo(cdcTableInfo.TableName.Table, cols, cdcTableInfo.IndexColumnsOffset)
  1689  		require.Equal(t, c.recovered, showCreateTable(t, recoveredTI))
  1690  		tableName := &model.TableName{Schema: "db", Table: "t1"}
  1691  		rowChange := sqlmodel.NewRowChange(tableName, nil, []interface{}{1, 1, 2}, nil, recoveredTI, nil, nil)
  1692  		sqlGot, argsGot := rowChange.GenSQL(sqlmodel.DMLDelete)
  1693  		require.Equal(t, "DELETE FROM `db`.`t1` WHERE `a1` = ? AND `a3` = ? LIMIT 1", sqlGot)
  1694  		require.Equal(t, []interface{}{1, 2}, argsGot)
  1695  
  1696  		sqlGot, argsGot = sqlmodel.GenDeleteSQL(rowChange, rowChange)
  1697  		require.Equal(t, "DELETE FROM `db`.`t1` WHERE (`a1` = ? AND `a3` = ?) OR (`a1` = ? AND `a3` = ?)", sqlGot)
  1698  		require.Equal(t, []interface{}{1, 2, 1, 2}, argsGot)
  1699  	}
  1700  }
  1701  
  1702  func TestFormatColVal(t *testing.T) {
  1703  	t.Parallel()
  1704  
  1705  	ftTypeFloatNotNull := types.NewFieldType(mysql.TypeFloat)
  1706  	ftTypeFloatNotNull.SetFlag(mysql.NotNullFlag)
  1707  	col := &timodel.ColumnInfo{FieldType: *ftTypeFloatNotNull}
  1708  
  1709  	var datum types.Datum
  1710  
  1711  	datum.SetFloat32(123.99)
  1712  	value, _, _, err := formatColVal(datum, col)
  1713  	require.NoError(t, err)
  1714  	require.EqualValues(t, float32(123.99), value)
  1715  
  1716  	datum.SetFloat32(float32(math.NaN()))
  1717  	value, _, warn, err := formatColVal(datum, col)
  1718  	require.NoError(t, err)
  1719  	require.Equal(t, float32(0), value)
  1720  	require.NotZero(t, warn)
  1721  
  1722  	datum.SetFloat32(float32(math.Inf(1)))
  1723  	value, _, warn, err = formatColVal(datum, col)
  1724  	require.NoError(t, err)
  1725  	require.Equal(t, float32(0), value)
  1726  	require.NotZero(t, warn)
  1727  
  1728  	datum.SetFloat32(float32(math.Inf(-1)))
  1729  	value, _, warn, err = formatColVal(datum, col)
  1730  	require.NoError(t, err)
  1731  	require.Equal(t, float32(0), value)
  1732  	require.NotZero(t, warn)
  1733  }