github.com/pingcap/tidb/parser@v0.0.0-20231013125129-93a834a6bf8d/model/model_test.go (about)

     1  // Copyright 2015 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 model
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tidb/parser/charset"
    23  	"github.com/pingcap/tidb/parser/mysql"
    24  	"github.com/pingcap/tidb/parser/types"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestT(t *testing.T) {
    29  	abc := NewCIStr("aBC")
    30  	require.Equal(t, "aBC", abc.O)
    31  	require.Equal(t, "abc", abc.L)
    32  	require.Equal(t, "aBC", abc.String())
    33  }
    34  
    35  func newColumnForTest(id int64, offset int) *ColumnInfo {
    36  	return &ColumnInfo{
    37  		ID:     id,
    38  		Name:   NewCIStr(fmt.Sprintf("c_%d", id)),
    39  		Offset: offset,
    40  	}
    41  }
    42  
    43  func newIndexForTest(id int64, cols ...*ColumnInfo) *IndexInfo {
    44  	idxCols := make([]*IndexColumn, 0, len(cols))
    45  	for _, c := range cols {
    46  		idxCols = append(idxCols, &IndexColumn{Offset: c.Offset, Name: c.Name})
    47  	}
    48  	return &IndexInfo{
    49  		ID:      id,
    50  		Name:    NewCIStr(fmt.Sprintf("i_%d", id)),
    51  		Columns: idxCols,
    52  	}
    53  }
    54  
    55  func checkOffsets(t *testing.T, tbl *TableInfo, ids ...int) {
    56  	require.Equal(t, len(ids), len(tbl.Columns))
    57  	for i := 0; i < len(ids); i++ {
    58  		expected := fmt.Sprintf("c_%d", ids[i])
    59  		require.Equal(t, expected, tbl.Columns[i].Name.L)
    60  		require.Equal(t, i, tbl.Columns[i].Offset)
    61  	}
    62  	for _, col := range tbl.Columns {
    63  		for _, idx := range tbl.Indices {
    64  			for _, idxCol := range idx.Columns {
    65  				if col.Name.L != idxCol.Name.L {
    66  					continue
    67  				}
    68  				// Columns with the same name should have a same offset.
    69  				require.Equal(t, col.Offset, idxCol.Offset)
    70  			}
    71  		}
    72  	}
    73  }
    74  
    75  func TestMoveColumnInfo(t *testing.T) {
    76  	c0 := newColumnForTest(0, 0)
    77  	c1 := newColumnForTest(1, 1)
    78  	c2 := newColumnForTest(2, 2)
    79  	c3 := newColumnForTest(3, 3)
    80  	c4 := newColumnForTest(4, 4)
    81  
    82  	i0 := newIndexForTest(0, c0, c1, c2, c3, c4)
    83  	i1 := newIndexForTest(1, c4, c2)
    84  	i2 := newIndexForTest(2, c0, c4)
    85  	i3 := newIndexForTest(3, c1, c2, c3)
    86  	i4 := newIndexForTest(4, c3, c2, c1)
    87  
    88  	tbl := &TableInfo{
    89  		ID:      1,
    90  		Name:    NewCIStr("t"),
    91  		Columns: []*ColumnInfo{c0, c1, c2, c3, c4},
    92  		Indices: []*IndexInfo{i0, i1, i2, i3, i4},
    93  	}
    94  
    95  	// Original offsets: [0, 1, 2, 3, 4]
    96  	tbl.MoveColumnInfo(4, 0)
    97  	checkOffsets(t, tbl, 4, 0, 1, 2, 3)
    98  	tbl.MoveColumnInfo(2, 3)
    99  	checkOffsets(t, tbl, 4, 0, 2, 1, 3)
   100  	tbl.MoveColumnInfo(3, 2)
   101  	checkOffsets(t, tbl, 4, 0, 1, 2, 3)
   102  	tbl.MoveColumnInfo(0, 4)
   103  	checkOffsets(t, tbl, 0, 1, 2, 3, 4)
   104  	tbl.MoveColumnInfo(2, 2)
   105  	checkOffsets(t, tbl, 0, 1, 2, 3, 4)
   106  	tbl.MoveColumnInfo(0, 0)
   107  	checkOffsets(t, tbl, 0, 1, 2, 3, 4)
   108  	tbl.MoveColumnInfo(1, 4)
   109  	checkOffsets(t, tbl, 0, 2, 3, 4, 1)
   110  	tbl.MoveColumnInfo(3, 0)
   111  	checkOffsets(t, tbl, 4, 0, 2, 3, 1)
   112  }
   113  
   114  func TestModelBasic(t *testing.T) {
   115  	column := &ColumnInfo{
   116  		ID:           1,
   117  		Name:         NewCIStr("c"),
   118  		Offset:       0,
   119  		DefaultValue: 0,
   120  		FieldType:    *types.NewFieldType(0),
   121  		Hidden:       true,
   122  	}
   123  	column.AddFlag(mysql.PriKeyFlag)
   124  
   125  	index := &IndexInfo{
   126  		Name:  NewCIStr("key"),
   127  		Table: NewCIStr("t"),
   128  		Columns: []*IndexColumn{
   129  			{
   130  				Name:   NewCIStr("c"),
   131  				Offset: 0,
   132  				Length: 10,
   133  			}},
   134  		Unique:  true,
   135  		Primary: true,
   136  	}
   137  
   138  	fk := &FKInfo{
   139  		RefCols: []CIStr{NewCIStr("a")},
   140  		Cols:    []CIStr{NewCIStr("a")},
   141  	}
   142  
   143  	seq := &SequenceInfo{
   144  		Increment: 1,
   145  		MinValue:  1,
   146  		MaxValue:  100,
   147  	}
   148  
   149  	table := &TableInfo{
   150  		ID:          1,
   151  		Name:        NewCIStr("t"),
   152  		Charset:     "utf8",
   153  		Collate:     "utf8_bin",
   154  		Columns:     []*ColumnInfo{column},
   155  		Indices:     []*IndexInfo{index},
   156  		ForeignKeys: []*FKInfo{fk},
   157  		PKIsHandle:  true,
   158  	}
   159  
   160  	table2 := &TableInfo{
   161  		ID:       2,
   162  		Name:     NewCIStr("s"),
   163  		Sequence: seq,
   164  	}
   165  
   166  	dbInfo := &DBInfo{
   167  		ID:      1,
   168  		Name:    NewCIStr("test"),
   169  		Charset: "utf8",
   170  		Collate: "utf8_bin",
   171  		Tables:  []*TableInfo{table},
   172  	}
   173  
   174  	n := dbInfo.Clone()
   175  	require.Equal(t, dbInfo, n)
   176  
   177  	pkName := table.GetPkName()
   178  	require.Equal(t, NewCIStr("c"), pkName)
   179  	newColumn := table.GetPkColInfo()
   180  	require.Equal(t, true, newColumn.Hidden)
   181  	require.Equal(t, column, newColumn)
   182  	inIdx := table.ColumnIsInIndex(column)
   183  	require.Equal(t, true, inIdx)
   184  	tp := IndexTypeBtree
   185  	require.Equal(t, "BTREE", tp.String())
   186  	tp = IndexTypeHash
   187  	require.Equal(t, "HASH", tp.String())
   188  	tp = 1e5
   189  	require.Equal(t, "", tp.String())
   190  	has := index.HasPrefixIndex()
   191  	require.Equal(t, true, has)
   192  	require.Equal(t, TSConvert2Time(table.UpdateTS), table.GetUpdateTime())
   193  	require.True(t, table2.IsSequence())
   194  	require.False(t, table2.IsBaseTable())
   195  
   196  	// Corner cases
   197  	column.ToggleFlag(mysql.PriKeyFlag)
   198  	pkName = table.GetPkName()
   199  	require.Equal(t, NewCIStr(""), pkName)
   200  	newColumn = table.GetPkColInfo()
   201  	require.Nil(t, newColumn)
   202  	anCol := &ColumnInfo{
   203  		Name: NewCIStr("d"),
   204  	}
   205  	exIdx := table.ColumnIsInIndex(anCol)
   206  	require.Equal(t, false, exIdx)
   207  	anIndex := &IndexInfo{
   208  		Columns: []*IndexColumn{},
   209  	}
   210  	no := anIndex.HasPrefixIndex()
   211  	require.Equal(t, false, no)
   212  
   213  	extraPK := NewExtraHandleColInfo()
   214  	require.Equal(t, mysql.NotNullFlag|mysql.PriKeyFlag, extraPK.GetFlag())
   215  	require.Equal(t, charset.CharsetBin, extraPK.GetCharset())
   216  	require.Equal(t, charset.CollationBin, extraPK.GetCollate())
   217  }
   218  
   219  func TestJobStartTime(t *testing.T) {
   220  	job := &Job{
   221  		ID:         123,
   222  		BinlogInfo: &HistoryInfo{},
   223  	}
   224  	require.Equal(t, TSConvert2Time(job.StartTS), time.Unix(0, 0))
   225  	require.Equal(t, fmt.Sprintf("ID:123, Type:none, State:none, SchemaState:none, SchemaID:0, TableID:0, RowCount:0, ArgLen:0, start time: %s, Err:<nil>, ErrCount:0, SnapshotVersion:0", time.Unix(0, 0)), job.String())
   226  }
   227  
   228  func TestJobCodec(t *testing.T) {
   229  	type A struct {
   230  		Name string
   231  	}
   232  	tzName, tzOffset := time.Now().In(time.UTC).Zone()
   233  	job := &Job{
   234  		ID:         1,
   235  		TableID:    2,
   236  		SchemaID:   1,
   237  		BinlogInfo: &HistoryInfo{},
   238  		Args:       []interface{}{NewCIStr("a"), A{Name: "abc"}},
   239  		ReorgMeta: &DDLReorgMeta{
   240  			Location: &TimeZoneLocation{Name: tzName, Offset: tzOffset},
   241  		},
   242  	}
   243  	job.BinlogInfo.AddDBInfo(123, &DBInfo{ID: 1, Name: NewCIStr("test_history_db")})
   244  	job.BinlogInfo.AddTableInfo(123, &TableInfo{ID: 1, Name: NewCIStr("test_history_tbl")})
   245  
   246  	// Test IsDependentOn.
   247  	// job: table ID is 2
   248  	// job1: table ID is 2
   249  	var err error
   250  	job1 := &Job{
   251  		ID:         2,
   252  		TableID:    2,
   253  		SchemaID:   1,
   254  		Type:       ActionRenameTable,
   255  		BinlogInfo: &HistoryInfo{},
   256  		Args:       []interface{}{int64(3), NewCIStr("new_table_name")},
   257  	}
   258  	job1.RawArgs, err = json.Marshal(job1.Args)
   259  	require.NoError(t, err)
   260  	isDependent, err := job.IsDependentOn(job1)
   261  	require.NoError(t, err)
   262  	require.True(t, isDependent)
   263  	// job1: rename table, old schema ID is 3
   264  	// job2: create schema, schema ID is 3
   265  	job2 := &Job{
   266  		ID:         3,
   267  		TableID:    3,
   268  		SchemaID:   3,
   269  		Type:       ActionCreateSchema,
   270  		BinlogInfo: &HistoryInfo{},
   271  	}
   272  	isDependent, err = job2.IsDependentOn(job1)
   273  	require.NoError(t, err)
   274  	require.True(t, isDependent)
   275  
   276  	// Test IsDependentOn for exchange partition with table.
   277  	// test ActionCreateSchema and ActionExchangeTablePartition is dependent.
   278  	job3 := &Job{
   279  		ID:         4,
   280  		TableID:    4,
   281  		SchemaID:   4,
   282  		Type:       ActionExchangeTablePartition,
   283  		BinlogInfo: &HistoryInfo{},
   284  		Args:       []interface{}{int64(6), int64(3), int64(5), "pt", true},
   285  	}
   286  	job3.RawArgs, err = json.Marshal(job3.Args)
   287  	require.NoError(t, err)
   288  	isDependent, err = job3.IsDependentOn(job2)
   289  	require.NoError(t, err)
   290  	require.True(t, isDependent)
   291  
   292  	// test random and ActionExchangeTablePartition is dependent because TableID is same.
   293  	job4 := &Job{
   294  		ID:         5,
   295  		TableID:    5,
   296  		SchemaID:   3,
   297  		Type:       ActionExchangeTablePartition,
   298  		BinlogInfo: &HistoryInfo{},
   299  		Args:       []interface{}{6, 4, 2, "pt", true},
   300  	}
   301  	job4.RawArgs, err = json.Marshal(job4.Args)
   302  	require.NoError(t, err)
   303  	isDependent, err = job4.IsDependentOn(job)
   304  	require.NoError(t, err)
   305  	require.True(t, isDependent)
   306  
   307  	// test ActionExchangeTablePartition and ActionExchangeTablePartition is dependent.
   308  	job5 := &Job{
   309  		ID:         6,
   310  		TableID:    6,
   311  		SchemaID:   6,
   312  		Type:       ActionExchangeTablePartition,
   313  		BinlogInfo: &HistoryInfo{},
   314  		Args:       []interface{}{2, 6, 5, "pt", true},
   315  	}
   316  	job5.RawArgs, err = json.Marshal(job5.Args)
   317  	require.NoError(t, err)
   318  	isDependent, err = job5.IsDependentOn(job4)
   319  	require.NoError(t, err)
   320  	require.True(t, isDependent)
   321  
   322  	job6 := &Job{
   323  		ID:         7,
   324  		TableID:    7,
   325  		SchemaID:   7,
   326  		Type:       ActionExchangeTablePartition,
   327  		BinlogInfo: &HistoryInfo{},
   328  		Args:       []interface{}{6, 4, 2, "pt", true},
   329  	}
   330  	job6.RawArgs, err = json.Marshal(job6.Args)
   331  	require.NoError(t, err)
   332  	isDependent, err = job6.IsDependentOn(job5)
   333  	require.NoError(t, err)
   334  	require.True(t, isDependent)
   335  
   336  	job7 := &Job{
   337  		ID:         8,
   338  		TableID:    8,
   339  		SchemaID:   8,
   340  		Type:       ActionExchangeTablePartition,
   341  		BinlogInfo: &HistoryInfo{},
   342  		Args:       []interface{}{8, 4, 6, "pt", true},
   343  	}
   344  	job7.RawArgs, err = json.Marshal(job7.Args)
   345  	require.NoError(t, err)
   346  	isDependent, err = job7.IsDependentOn(job6)
   347  	require.NoError(t, err)
   348  	require.True(t, isDependent)
   349  
   350  	job8 := &Job{
   351  		ID:         9,
   352  		TableID:    9,
   353  		SchemaID:   9,
   354  		Type:       ActionExchangeTablePartition,
   355  		BinlogInfo: &HistoryInfo{},
   356  		Args:       []interface{}{8, 9, 9, "pt", true},
   357  	}
   358  	job8.RawArgs, err = json.Marshal(job8.Args)
   359  	require.NoError(t, err)
   360  	isDependent, err = job8.IsDependentOn(job7)
   361  	require.NoError(t, err)
   362  	require.True(t, isDependent)
   363  
   364  	job9 := &Job{
   365  		ID:         10,
   366  		TableID:    10,
   367  		SchemaID:   10,
   368  		Type:       ActionExchangeTablePartition,
   369  		BinlogInfo: &HistoryInfo{},
   370  		Args:       []interface{}{10, 10, 8, "pt", true},
   371  	}
   372  	job9.RawArgs, err = json.Marshal(job9.Args)
   373  	require.NoError(t, err)
   374  	isDependent, err = job9.IsDependentOn(job8)
   375  	require.NoError(t, err)
   376  	require.True(t, isDependent)
   377  
   378  	// test ActionDropSchema and ActionExchangeTablePartition is dependent.
   379  	job10 := &Job{
   380  		ID:         11,
   381  		TableID:    11,
   382  		SchemaID:   11,
   383  		Type:       ActionDropSchema,
   384  		BinlogInfo: &HistoryInfo{},
   385  	}
   386  	job10.RawArgs, err = json.Marshal(job10.Args)
   387  	require.NoError(t, err)
   388  
   389  	job11 := &Job{
   390  		ID:         12,
   391  		TableID:    12,
   392  		SchemaID:   11,
   393  		Type:       ActionExchangeTablePartition,
   394  		BinlogInfo: &HistoryInfo{},
   395  		Args:       []interface{}{10, 10, 8, "pt", true},
   396  	}
   397  	job11.RawArgs, err = json.Marshal(job11.Args)
   398  	require.NoError(t, err)
   399  	isDependent, err = job11.IsDependentOn(job10)
   400  	require.NoError(t, err)
   401  	require.True(t, isDependent)
   402  
   403  	// test ActionDropTable and ActionExchangeTablePartition is dependent.
   404  	job12 := &Job{
   405  		ID:         13,
   406  		TableID:    13,
   407  		SchemaID:   11,
   408  		Type:       ActionDropTable,
   409  		BinlogInfo: &HistoryInfo{},
   410  	}
   411  	job12.RawArgs, err = json.Marshal(job12.Args)
   412  	require.NoError(t, err)
   413  	isDependent, err = job11.IsDependentOn(job12)
   414  	require.NoError(t, err)
   415  	require.False(t, isDependent)
   416  
   417  	job13 := &Job{
   418  		ID:         14,
   419  		TableID:    12,
   420  		SchemaID:   14,
   421  		Type:       ActionDropTable,
   422  		BinlogInfo: &HistoryInfo{},
   423  	}
   424  	job13.RawArgs, err = json.Marshal(job13.Args)
   425  	require.NoError(t, err)
   426  	isDependent, err = job11.IsDependentOn(job13)
   427  	require.NoError(t, err)
   428  	require.True(t, isDependent)
   429  
   430  	// test ActionDropTable and ActionExchangeTablePartition is dependent.
   431  	job14 := &Job{
   432  		ID:         15,
   433  		TableID:    15,
   434  		SchemaID:   15,
   435  		Type:       ActionExchangeTablePartition,
   436  		BinlogInfo: &HistoryInfo{},
   437  		Args:       []interface{}{16, 17, 12, "pt", true},
   438  	}
   439  	job14.RawArgs, err = json.Marshal(job14.Args)
   440  	require.NoError(t, err)
   441  	isDependent, err = job13.IsDependentOn(job14)
   442  	require.NoError(t, err)
   443  	require.True(t, isDependent)
   444  
   445  	// test ActionFlashbackCluster with other ddl jobs are dependent.
   446  	job15 := &Job{
   447  		ID:         16,
   448  		Type:       ActionFlashbackCluster,
   449  		BinlogInfo: &HistoryInfo{},
   450  		Args:       []interface{}{0, map[string]interface{}{}, "ON", true},
   451  	}
   452  	job15.RawArgs, err = json.Marshal(job15.Args)
   453  	require.NoError(t, err)
   454  	isDependent, err = job.IsDependentOn(job15)
   455  	require.NoError(t, err)
   456  	require.True(t, isDependent)
   457  
   458  	require.Equal(t, false, job.IsCancelled())
   459  	b, err := job.Encode(false)
   460  	require.NoError(t, err)
   461  	newJob := &Job{}
   462  	err = newJob.Decode(b)
   463  	require.NoError(t, err)
   464  	require.Equal(t, job.BinlogInfo, newJob.BinlogInfo)
   465  	name := CIStr{}
   466  	a := A{}
   467  	err = newJob.DecodeArgs(&name, &a)
   468  	require.NoError(t, err)
   469  	require.Equal(t, NewCIStr(""), name)
   470  	require.Equal(t, A{Name: ""}, a)
   471  	require.Greater(t, len(newJob.String()), 0)
   472  	require.Equal(t, newJob.ReorgMeta.Location.Name, tzName)
   473  	require.Equal(t, newJob.ReorgMeta.Location.Offset, tzOffset)
   474  
   475  	job.BinlogInfo.Clean()
   476  	b1, err := job.Encode(true)
   477  	require.NoError(t, err)
   478  	newJob = &Job{}
   479  	err = newJob.Decode(b1)
   480  	require.NoError(t, err)
   481  	require.Equal(t, &HistoryInfo{}, newJob.BinlogInfo)
   482  	name = CIStr{}
   483  	a = A{}
   484  	err = newJob.DecodeArgs(&name, &a)
   485  	require.NoError(t, err)
   486  	require.Equal(t, NewCIStr("a"), name)
   487  	require.Equal(t, A{Name: "abc"}, a)
   488  	require.Greater(t, len(newJob.String()), 0)
   489  
   490  	b2, err := job.Encode(true)
   491  	require.NoError(t, err)
   492  	newJob = &Job{}
   493  	err = newJob.Decode(b2)
   494  	require.NoError(t, err)
   495  	name = CIStr{}
   496  	// Don't decode to a here.
   497  	err = newJob.DecodeArgs(&name)
   498  	require.NoError(t, err)
   499  	require.Equal(t, NewCIStr("a"), name)
   500  	require.Greater(t, len(newJob.String()), 0)
   501  
   502  	job.State = JobStateDone
   503  	require.True(t, job.IsDone())
   504  	require.True(t, job.IsFinished())
   505  	require.False(t, job.IsRunning())
   506  	require.False(t, job.IsSynced())
   507  	require.False(t, job.IsRollbackDone())
   508  	job.SetRowCount(3)
   509  	require.Equal(t, int64(3), job.GetRowCount())
   510  }
   511  
   512  func TestState(t *testing.T) {
   513  	schemaTbl := []SchemaState{
   514  		StateDeleteOnly,
   515  		StateWriteOnly,
   516  		StateWriteReorganization,
   517  		StateDeleteReorganization,
   518  		StatePublic,
   519  		StateGlobalTxnOnly,
   520  	}
   521  
   522  	for _, state := range schemaTbl {
   523  		require.Greater(t, len(state.String()), 0)
   524  	}
   525  
   526  	jobTbl := []JobState{
   527  		JobStateRunning,
   528  		JobStateDone,
   529  		JobStateCancelled,
   530  		JobStateRollingback,
   531  		JobStateRollbackDone,
   532  		JobStateSynced,
   533  	}
   534  
   535  	for _, state := range jobTbl {
   536  		require.Greater(t, len(state.String()), 0)
   537  	}
   538  }
   539  
   540  func TestString(t *testing.T) {
   541  	acts := []struct {
   542  		act    ActionType
   543  		result string
   544  	}{
   545  		{ActionNone, "none"},
   546  		{ActionAddForeignKey, "add foreign key"},
   547  		{ActionDropForeignKey, "drop foreign key"},
   548  		{ActionTruncateTable, "truncate table"},
   549  		{ActionModifyColumn, "modify column"},
   550  		{ActionRenameTable, "rename table"},
   551  		{ActionRenameTables, "rename tables"},
   552  		{ActionSetDefaultValue, "set default value"},
   553  		{ActionCreateSchema, "create schema"},
   554  		{ActionDropSchema, "drop schema"},
   555  		{ActionCreateTable, "create table"},
   556  		{ActionDropTable, "drop table"},
   557  		{ActionAddIndex, "add index"},
   558  		{ActionDropIndex, "drop index"},
   559  		{ActionAddColumn, "add column"},
   560  		{ActionDropColumn, "drop column"},
   561  		{ActionModifySchemaCharsetAndCollate, "modify schema charset and collate"},
   562  		{ActionAlterTablePlacement, "alter table placement"},
   563  		{ActionAlterTablePartitionPlacement, "alter table partition placement"},
   564  		{ActionAlterNoCacheTable, "alter table nocache"},
   565  	}
   566  
   567  	for _, v := range acts {
   568  		str := v.act.String()
   569  		require.Equal(t, v.result, str)
   570  	}
   571  }
   572  
   573  func TestUnmarshalCIStr(t *testing.T) {
   574  	var ci CIStr
   575  
   576  	// Test unmarshal CIStr from a single string.
   577  	str := "aaBB"
   578  	buf, err := json.Marshal(str)
   579  	require.NoError(t, err)
   580  	require.NoError(t, ci.UnmarshalJSON(buf))
   581  	require.Equal(t, str, ci.O)
   582  	require.Equal(t, "aabb", ci.L)
   583  
   584  	buf, err = json.Marshal(ci)
   585  	require.NoError(t, err)
   586  	require.Equal(t, `{"O":"aaBB","L":"aabb"}`, string(buf))
   587  	require.NoError(t, ci.UnmarshalJSON(buf))
   588  	require.Equal(t, str, ci.O)
   589  	require.Equal(t, "aabb", ci.L)
   590  }
   591  
   592  func TestDefaultValue(t *testing.T) {
   593  	srcCol := &ColumnInfo{
   594  		ID: 1,
   595  	}
   596  	randPlainStr := "random_plain_string"
   597  
   598  	oldPlainCol := srcCol.Clone()
   599  	oldPlainCol.Name = NewCIStr("oldPlainCol")
   600  	oldPlainCol.FieldType = *types.NewFieldType(mysql.TypeLong)
   601  	oldPlainCol.DefaultValue = randPlainStr
   602  	oldPlainCol.OriginDefaultValue = randPlainStr
   603  
   604  	newPlainCol := srcCol.Clone()
   605  	newPlainCol.Name = NewCIStr("newPlainCol")
   606  	newPlainCol.FieldType = *types.NewFieldType(mysql.TypeLong)
   607  	err := newPlainCol.SetDefaultValue(1)
   608  	require.NoError(t, err)
   609  	require.Equal(t, 1, newPlainCol.GetDefaultValue())
   610  	err = newPlainCol.SetDefaultValue(randPlainStr)
   611  	require.NoError(t, err)
   612  	require.Equal(t, randPlainStr, newPlainCol.GetDefaultValue())
   613  
   614  	randBitStr := string([]byte{25, 185})
   615  
   616  	oldBitCol := srcCol.Clone()
   617  	oldBitCol.Name = NewCIStr("oldBitCol")
   618  	oldBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
   619  	oldBitCol.DefaultValue = randBitStr
   620  	oldBitCol.OriginDefaultValue = randBitStr
   621  
   622  	newBitCol := srcCol.Clone()
   623  	newBitCol.Name = NewCIStr("newBitCol")
   624  	newBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
   625  	err = newBitCol.SetDefaultValue(1)
   626  	// Only string type is allowed in BIT column.
   627  	require.Error(t, err)
   628  	require.Contains(t, err.Error(), "Invalid default value")
   629  	require.Equal(t, 1, newBitCol.GetDefaultValue())
   630  	err = newBitCol.SetDefaultValue(randBitStr)
   631  	require.NoError(t, err)
   632  	require.Equal(t, randBitStr, newBitCol.GetDefaultValue())
   633  
   634  	nullBitCol := srcCol.Clone()
   635  	nullBitCol.Name = NewCIStr("nullBitCol")
   636  	nullBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
   637  	err = nullBitCol.SetOriginDefaultValue(nil)
   638  	require.NoError(t, err)
   639  	require.Nil(t, nullBitCol.GetOriginDefaultValue())
   640  
   641  	testCases := []struct {
   642  		col          *ColumnInfo
   643  		isConsistent bool
   644  	}{
   645  		{oldPlainCol, true},
   646  		{oldBitCol, false},
   647  		{newPlainCol, true},
   648  		{newBitCol, true},
   649  		{nullBitCol, true},
   650  	}
   651  	for _, tc := range testCases {
   652  		col, isConsistent := tc.col, tc.isConsistent
   653  		comment := fmt.Sprintf("%s assertion failed", col.Name.O)
   654  		bytes, err := json.Marshal(col)
   655  		require.NoError(t, err, comment)
   656  		var newCol ColumnInfo
   657  		err = json.Unmarshal(bytes, &newCol)
   658  		require.NoError(t, err, comment)
   659  		if isConsistent {
   660  			require.Equal(t, col.GetDefaultValue(), newCol.GetDefaultValue(), comment)
   661  			require.Equal(t, col.GetOriginDefaultValue(), newCol.GetOriginDefaultValue(), comment)
   662  		} else {
   663  			require.NotEqual(t, col.GetDefaultValue(), newCol.GetDefaultValue(), comment)
   664  			require.NotEqual(t, col.GetOriginDefaultValue(), newCol.GetOriginDefaultValue(), comment)
   665  		}
   666  	}
   667  }
   668  
   669  func TestPlacementSettingsString(t *testing.T) {
   670  	settings := &PlacementSettings{
   671  		PrimaryRegion: "us-east-1",
   672  		Regions:       "us-east-1,us-east-2",
   673  		Schedule:      "EVEN",
   674  	}
   675  	require.Equal(t, "PRIMARY_REGION=\"us-east-1\" REGIONS=\"us-east-1,us-east-2\" SCHEDULE=\"EVEN\"", settings.String())
   676  
   677  	settings = &PlacementSettings{
   678  		LeaderConstraints: "[+region=bj]",
   679  	}
   680  	require.Equal(t, "LEADER_CONSTRAINTS=\"[+region=bj]\"", settings.String())
   681  
   682  	settings = &PlacementSettings{
   683  		Voters:              1,
   684  		VoterConstraints:    "[+region=us-east-1]",
   685  		Followers:           2,
   686  		FollowerConstraints: "[+disk=ssd]",
   687  		Learners:            3,
   688  		LearnerConstraints:  "[+region=us-east-2]",
   689  	}
   690  	require.Equal(t, "VOTERS=1 VOTER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=2 FOLLOWER_CONSTRAINTS=\"[+disk=ssd]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=us-east-2]\"", settings.String())
   691  
   692  	settings = &PlacementSettings{
   693  		Voters:      3,
   694  		Followers:   2,
   695  		Learners:    1,
   696  		Constraints: "{\"+us-east-1\":1,+us-east-2:1}",
   697  	}
   698  	require.Equal(t, "CONSTRAINTS=\"{\\\"+us-east-1\\\":1,+us-east-2:1}\" VOTERS=3 FOLLOWERS=2 LEARNERS=1", settings.String())
   699  }
   700  
   701  func TestPlacementSettingsClone(t *testing.T) {
   702  	settings := &PlacementSettings{}
   703  	clonedSettings := settings.Clone()
   704  	clonedSettings.PrimaryRegion = "r1"
   705  	clonedSettings.Regions = "r1,r2"
   706  	clonedSettings.Followers = 1
   707  	clonedSettings.Voters = 2
   708  	clonedSettings.Followers = 3
   709  	clonedSettings.Constraints = "[+zone=z1]"
   710  	clonedSettings.LearnerConstraints = "[+region=r1]"
   711  	clonedSettings.FollowerConstraints = "[+disk=ssd]"
   712  	clonedSettings.LeaderConstraints = "[+region=r2]"
   713  	clonedSettings.VoterConstraints = "[+zone=z2]"
   714  	clonedSettings.Schedule = "even"
   715  	require.Equal(t, PlacementSettings{}, *settings)
   716  }
   717  
   718  func TestPlacementPolicyClone(t *testing.T) {
   719  	policy := &PolicyInfo{
   720  		PlacementSettings: &PlacementSettings{},
   721  	}
   722  	clonedPolicy := policy.Clone()
   723  	clonedPolicy.ID = 100
   724  	clonedPolicy.Name = NewCIStr("p2")
   725  	clonedPolicy.State = StateDeleteOnly
   726  	clonedPolicy.PlacementSettings.Followers = 10
   727  
   728  	require.Equal(t, int64(0), policy.ID)
   729  	require.Equal(t, NewCIStr(""), policy.Name)
   730  	require.Equal(t, StateNone, policy.State)
   731  	require.Equal(t, PlacementSettings{}, *(policy.PlacementSettings))
   732  }
   733  
   734  func TestLocation(t *testing.T) {
   735  	// test offset = 0
   736  	loc := &TimeZoneLocation{}
   737  	nLoc, err := loc.GetLocation()
   738  	require.NoError(t, err)
   739  	require.Equal(t, nLoc.String(), "UTC")
   740  	// test loc.location != nil
   741  	loc.Name = "Asia/Shanghai"
   742  	nLoc, err = loc.GetLocation()
   743  	require.NoError(t, err)
   744  	require.Equal(t, nLoc.String(), "UTC")
   745  	// timezone +05:00
   746  	loc1 := &TimeZoneLocation{Name: "UTC", Offset: 18000}
   747  	loc1Byte, err := json.Marshal(loc1)
   748  	require.NoError(t, err)
   749  	loc2 := &TimeZoneLocation{}
   750  	err = json.Unmarshal(loc1Byte, loc2)
   751  	require.NoError(t, err)
   752  	require.Equal(t, loc2.Offset, loc1.Offset)
   753  	require.Equal(t, loc2.Name, loc1.Name)
   754  	nLoc, err = loc2.GetLocation()
   755  	require.NoError(t, err)
   756  	require.Equal(t, nLoc.String(), "UTC")
   757  	location := time.FixedZone("UTC", loc1.Offset)
   758  	require.Equal(t, nLoc, location)
   759  }
   760  
   761  func TestIsIndexPrefixCovered(t *testing.T) {
   762  	c0 := newColumnForTest(0, 0)
   763  	c1 := newColumnForTest(1, 1)
   764  	c2 := newColumnForTest(2, 2)
   765  	c3 := newColumnForTest(3, 3)
   766  	c4 := newColumnForTest(4, 4)
   767  
   768  	i0 := newIndexForTest(0, c0, c1, c2)
   769  	i1 := newIndexForTest(1, c4, c2)
   770  
   771  	tbl := &TableInfo{
   772  		ID:      1,
   773  		Name:    NewCIStr("t"),
   774  		Columns: []*ColumnInfo{c0, c1, c2, c3, c4},
   775  		Indices: []*IndexInfo{i0, i1},
   776  	}
   777  	require.Equal(t, true, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_0")))
   778  	require.Equal(t, true, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_0"), NewCIStr("c_1"), NewCIStr("c_2")))
   779  	require.Equal(t, false, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_1")))
   780  	require.Equal(t, false, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_2")))
   781  	require.Equal(t, false, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_1"), NewCIStr("c_2")))
   782  	require.Equal(t, false, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_0"), NewCIStr("c_2")))
   783  
   784  	require.Equal(t, true, IsIndexPrefixCovered(tbl, i1, NewCIStr("c_4")))
   785  	require.Equal(t, true, IsIndexPrefixCovered(tbl, i1, NewCIStr("c_4"), NewCIStr("c_2")))
   786  	require.Equal(t, false, IsIndexPrefixCovered(tbl, i0, NewCIStr("c_2")))
   787  }
   788  
   789  func TestTTLInfoClone(t *testing.T) {
   790  	ttlInfo := &TTLInfo{
   791  		ColumnName:       NewCIStr("test"),
   792  		IntervalExprStr:  "test_expr",
   793  		IntervalTimeUnit: 5,
   794  		Enable:           true,
   795  	}
   796  
   797  	clonedTTLInfo := ttlInfo.Clone()
   798  	clonedTTLInfo.ColumnName = NewCIStr("test_2")
   799  	clonedTTLInfo.IntervalExprStr = "test_expr_2"
   800  	clonedTTLInfo.IntervalTimeUnit = 9
   801  	clonedTTLInfo.Enable = false
   802  
   803  	require.Equal(t, "test", ttlInfo.ColumnName.O)
   804  	require.Equal(t, "test_expr", ttlInfo.IntervalExprStr)
   805  	require.Equal(t, 5, ttlInfo.IntervalTimeUnit)
   806  	require.Equal(t, true, ttlInfo.Enable)
   807  }
   808  
   809  func TestTTLJobInterval(t *testing.T) {
   810  	ttlInfo := &TTLInfo{}
   811  
   812  	interval, err := ttlInfo.GetJobInterval()
   813  	require.NoError(t, err)
   814  	require.Equal(t, time.Hour, interval)
   815  
   816  	ttlInfo = &TTLInfo{JobInterval: "200h"}
   817  	interval, err = ttlInfo.GetJobInterval()
   818  	require.NoError(t, err)
   819  	require.Equal(t, time.Hour*200, interval)
   820  }