github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/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  	"context"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/pingcap/check"
    22  	"github.com/pingcap/log"
    23  	"github.com/pingcap/parser/mysql"
    24  	"github.com/pingcap/ticdc/cdc/model"
    25  	"github.com/pingcap/ticdc/pkg/regionspan"
    26  	"github.com/pingcap/ticdc/pkg/util/testleak"
    27  	ticonfig "github.com/pingcap/tidb/config"
    28  	tidbkv "github.com/pingcap/tidb/kv"
    29  	"github.com/pingcap/tidb/session"
    30  	"github.com/pingcap/tidb/store/mockstore"
    31  	"github.com/pingcap/tidb/store/tikv/oracle"
    32  	"github.com/pingcap/tidb/util/testkit"
    33  	"go.uber.org/zap"
    34  )
    35  
    36  type mountTxnsSuite struct{}
    37  
    38  var _ = check.Suite(&mountTxnsSuite{})
    39  
    40  func (s *mountTxnsSuite) TestMounterDisableOldValue(c *check.C) {
    41  	defer testleak.AfterTest(c)()
    42  	testCases := []struct {
    43  		tableName      string
    44  		createTableDDL string
    45  		values         [][]interface{}
    46  	}{{
    47  		tableName:      "simple",
    48  		createTableDDL: "create table simple(id int primary key)",
    49  		values:         [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    50  	}, {
    51  		tableName:      "no_pk",
    52  		createTableDDL: "create table no_pk(id int not null unique key)",
    53  		values:         [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    54  	}, {
    55  		tableName:      "many_index",
    56  		createTableDDL: "create table many_index(id int not null unique key, c1 int unique key, c2 int, INDEX (c2))",
    57  		values:         [][]interface{}{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}},
    58  	}, {
    59  		tableName:      "default_value",
    60  		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')",
    61  		values:         [][]interface{}{{1}, {2}, {3}, {4}, {5}},
    62  	}, {
    63  		tableName: "partition_table",
    64  		createTableDDL: `CREATE TABLE partition_table  (
    65  			id INT NOT NULL AUTO_INCREMENT UNIQUE KEY,
    66  			fname VARCHAR(25) NOT NULL,
    67  			lname VARCHAR(25) NOT NULL,
    68  			store_id INT NOT NULL,
    69  			department_id INT NOT NULL,
    70  			INDEX (department_id)
    71  		)
    72  
    73  		PARTITION BY RANGE(id)  (
    74  			PARTITION p0 VALUES LESS THAN (5),
    75  			PARTITION p1 VALUES LESS THAN (10),
    76  			PARTITION p2 VALUES LESS THAN (15),
    77  			PARTITION p3 VALUES LESS THAN (20)
    78  		)`,
    79  		values: [][]interface{}{
    80  			{1, "aa", "bb", 12, 12},
    81  			{6, "aac", "bab", 51, 51},
    82  			{11, "aad", "bsb", 71, 61},
    83  			{18, "aae", "bbf", 21, 14},
    84  			{15, "afa", "bbc", 11, 12},
    85  		},
    86  	}, {
    87  		tableName: "tp_int",
    88  		createTableDDL: `create table tp_int
    89  		(
    90  			id          int auto_increment,
    91  			c_tinyint   tinyint   null,
    92  			c_smallint  smallint  null,
    93  			c_mediumint mediumint null,
    94  			c_int       int       null,
    95  			c_bigint    bigint    null,
    96  			constraint pk
    97  				primary key (id)
    98  		);`,
    99  		values: [][]interface{}{
   100  			{1, 1, 2, 3, 4, 5},
   101  			{2},
   102  			{3, 3, 4, 5, 6, 7},
   103  			{4, 127, 32767, 8388607, 2147483647, 9223372036854775807},
   104  			{5, -128, -32768, -8388608, -2147483648, -9223372036854775808},
   105  		},
   106  	}, {
   107  		tableName: "tp_text",
   108  		createTableDDL: `create table tp_text
   109  		(
   110  			id           int auto_increment,
   111  			c_tinytext   tinytext      null,
   112  			c_text       text          null,
   113  			c_mediumtext mediumtext    null,
   114  			c_longtext   longtext      null,
   115  			c_varchar    varchar(16)   null,
   116  			c_char       char(16)      null,
   117  			c_tinyblob   tinyblob      null,
   118  			c_blob       blob          null,
   119  			c_mediumblob mediumblob    null,
   120  			c_longblob   longblob      null,
   121  			c_binary     binary(16)    null,
   122  			c_varbinary  varbinary(16) null,
   123  			constraint pk
   124  				primary key (id)
   125  		);`,
   126  		values: [][]interface{}{
   127  			{1},
   128  			{
   129  				2, "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A", "89504E470D0A1A0A",
   130  				"89504E470D0A1A0A",
   131  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   132  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   133  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   134  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   135  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   136  				[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
   137  			},
   138  			{
   139  				3, "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free", "bug free",
   140  				"bug free", "bug free", "bug free", "bug free",
   141  			},
   142  			{4, "", "", "", "", "", "", "", "", "", "", "", ""},
   143  			{5, "你好", "我好", "大家好", "道路", "千万条", "安全", "第一条", "行车", "不规范", "亲人", "两行泪", "!"},
   144  			{6, "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "☺️", "😊", "😇", "🙂"},
   145  		},
   146  	}, {
   147  		tableName: "tp_time",
   148  		createTableDDL: `create table tp_time
   149  		(
   150  			id          int auto_increment,
   151  			c_date      date      null,
   152  			c_datetime  datetime  null,
   153  			c_timestamp timestamp null,
   154  			c_time      time      null,
   155  			c_year      year      null,
   156  			constraint pk
   157  				primary key (id)
   158  		);`,
   159  		values: [][]interface{}{
   160  			{1},
   161  			{2, "2020-02-20", "2020-02-20 02:20:20", "2020-02-20 02:20:20", "02:20:20", "2020"},
   162  		},
   163  	}, {
   164  		tableName: "tp_real",
   165  		createTableDDL: `create table tp_real
   166  		(
   167  			id        int auto_increment,
   168  			c_float   float   null,
   169  			c_double  double  null,
   170  			c_decimal decimal null,
   171  			constraint pk
   172  				primary key (id)
   173  		);`,
   174  		values: [][]interface{}{
   175  			{1},
   176  			{2, "2020.0202", "2020.0303", "2020.0404"},
   177  		},
   178  	}, {
   179  		tableName: "tp_other",
   180  		createTableDDL: `create table tp_other
   181  		(
   182  			id     int auto_increment,
   183  			c_enum enum ('a','b','c') null,
   184  			c_set  set ('a','b','c')  null,
   185  			c_bit  bit(64)            null,
   186  			c_json json               null,
   187  			constraint pk
   188  				primary key (id)
   189  		);`,
   190  		values: [][]interface{}{
   191  			{1},
   192  			{2, "a", "a,c", 888, `{"aa":"bb"}`},
   193  		},
   194  	}, {
   195  		tableName:      "clustered_index1",
   196  		createTableDDL: "CREATE TABLE clustered_index1 (id VARCHAR(255) PRIMARY KEY, data INT);",
   197  		values: [][]interface{}{
   198  			{"hhh"},
   199  			{"你好😘", 666},
   200  			{"世界🤪", 888},
   201  		},
   202  	}, {
   203  		tableName:      "clustered_index2",
   204  		createTableDDL: "CREATE TABLE clustered_index2 (id VARCHAR(255), data INT, ddaa date, PRIMARY KEY (id, data, ddaa), UNIQUE KEY (id, data, ddaa));",
   205  		values: [][]interface{}{
   206  			{"你好😘", 666, "2020-11-20"},
   207  			{"世界🤪", 888, "2020-05-12"},
   208  		},
   209  	}}
   210  	for _, tc := range testCases {
   211  		testMounterDisableOldValue(c, tc)
   212  	}
   213  }
   214  
   215  func testMounterDisableOldValue(c *check.C, tc struct {
   216  	tableName      string
   217  	createTableDDL string
   218  	values         [][]interface{}
   219  }) {
   220  	store, err := mockstore.NewMockStore()
   221  	c.Assert(err, check.IsNil)
   222  	defer store.Close() //nolint:errcheck
   223  	ticonfig.UpdateGlobal(func(conf *ticonfig.Config) {
   224  		// we can update the tidb config here
   225  	})
   226  	session.SetSchemaLease(0)
   227  	session.DisableStats4Test()
   228  	domain, err := session.BootstrapSession(store)
   229  	c.Assert(err, check.IsNil)
   230  	defer domain.Close()
   231  	domain.SetStatsUpdating(true)
   232  	tk := testkit.NewTestKit(c, store)
   233  	tk.MustExec("set @@tidb_enable_clustered_index=1;")
   234  	tk.MustExec("use test;")
   235  
   236  	tk.MustExec(tc.createTableDDL)
   237  
   238  	jobs, err := getAllHistoryDDLJob(store)
   239  	c.Assert(err, check.IsNil)
   240  	scheamStorage, err := NewSchemaStorage(nil, 0, nil, false)
   241  	c.Assert(err, check.IsNil)
   242  	for _, job := range jobs {
   243  		err := scheamStorage.HandleDDLJob(job)
   244  		c.Assert(err, check.IsNil)
   245  	}
   246  	tableInfo, ok := scheamStorage.GetLastSnapshot().GetTableByName("test", tc.tableName)
   247  	c.Assert(ok, check.IsTrue)
   248  	if tableInfo.IsCommonHandle {
   249  		// we can check this log to make sure if the clustered-index is enabled
   250  		log.Info("this table is enable the clustered index", zap.String("tableName", tableInfo.Name.L))
   251  	}
   252  
   253  	for _, params := range tc.values {
   254  		insertSQL := prepareInsertSQL(c, tableInfo, len(params))
   255  		tk.MustExec(insertSQL, params...)
   256  	}
   257  
   258  	ver, err := store.CurrentVersion(oracle.GlobalTxnScope)
   259  	c.Assert(err, check.IsNil)
   260  	scheamStorage.AdvanceResolvedTs(ver.Ver)
   261  	mounter := NewMounter(scheamStorage, 1, false).(*mounterImpl)
   262  	mounter.tz = time.Local
   263  	ctx := context.Background()
   264  
   265  	mountAndCheckRowInTable := func(tableID int64, f func(key []byte, value []byte) *model.RawKVEntry) int {
   266  		var rows int
   267  		walkTableSpanInStore(c, store, tableID, func(key []byte, value []byte) {
   268  			rawKV := f(key, value)
   269  			row, err := mounter.unmarshalAndMountRowChanged(ctx, rawKV)
   270  			c.Assert(err, check.IsNil)
   271  			if row == nil {
   272  				return
   273  			}
   274  			rows++
   275  			c.Assert(row.Table.Table, check.Equals, tc.tableName)
   276  			c.Assert(row.Table.Schema, check.Equals, "test")
   277  			// TODO: test column flag, column type and index columns
   278  			if len(row.Columns) != 0 {
   279  				checkSQL, params := prepareCheckSQL(c, tc.tableName, row.Columns)
   280  				result := tk.MustQuery(checkSQL, params...)
   281  				result.Check([][]interface{}{{"1"}})
   282  			}
   283  			if len(row.PreColumns) != 0 {
   284  				checkSQL, params := prepareCheckSQL(c, tc.tableName, row.PreColumns)
   285  				result := tk.MustQuery(checkSQL, params...)
   286  				result.Check([][]interface{}{{"1"}})
   287  			}
   288  		})
   289  		return rows
   290  	}
   291  
   292  	mountAndCheckRow := func(f func(key []byte, value []byte) *model.RawKVEntry) int {
   293  		partitionInfo := tableInfo.GetPartitionInfo()
   294  		if partitionInfo == nil {
   295  			return mountAndCheckRowInTable(tableInfo.ID, f)
   296  		}
   297  		var rows int
   298  		for _, p := range partitionInfo.Definitions {
   299  			rows += mountAndCheckRowInTable(p.ID, f)
   300  		}
   301  		return rows
   302  	}
   303  
   304  	rows := mountAndCheckRow(func(key []byte, value []byte) *model.RawKVEntry {
   305  		return &model.RawKVEntry{
   306  			OpType:  model.OpTypePut,
   307  			Key:     key,
   308  			Value:   value,
   309  			StartTs: ver.Ver - 1,
   310  			CRTs:    ver.Ver,
   311  		}
   312  	})
   313  	c.Assert(rows, check.Equals, len(tc.values))
   314  
   315  	rows = mountAndCheckRow(func(key []byte, value []byte) *model.RawKVEntry {
   316  		return &model.RawKVEntry{
   317  			OpType:  model.OpTypeDelete,
   318  			Key:     key,
   319  			Value:   nil, // delete event doesn't include a value when old-value is disabled
   320  			StartTs: ver.Ver - 1,
   321  			CRTs:    ver.Ver,
   322  		}
   323  	})
   324  	c.Assert(rows, check.Equals, len(tc.values))
   325  }
   326  
   327  func prepareInsertSQL(c *check.C, tableInfo *model.TableInfo, columnLens int) string {
   328  	var sb strings.Builder
   329  	_, err := sb.WriteString("INSERT INTO " + tableInfo.Name.O + "(")
   330  	c.Assert(err, check.IsNil)
   331  	for i := 0; i < columnLens; i++ {
   332  		col := tableInfo.Columns[i]
   333  		if i != 0 {
   334  			_, err = sb.WriteString(", ")
   335  			c.Assert(err, check.IsNil)
   336  		}
   337  		_, err = sb.WriteString(col.Name.O)
   338  		c.Assert(err, check.IsNil)
   339  	}
   340  	_, err = sb.WriteString(") VALUES (")
   341  	c.Assert(err, check.IsNil)
   342  	for i := 0; i < columnLens; i++ {
   343  		if i != 0 {
   344  			_, err = sb.WriteString(", ")
   345  			c.Assert(err, check.IsNil)
   346  		}
   347  		_, err = sb.WriteString("?")
   348  		c.Assert(err, check.IsNil)
   349  	}
   350  	_, err = sb.WriteString(")")
   351  	c.Assert(err, check.IsNil)
   352  	return sb.String()
   353  }
   354  
   355  func prepareCheckSQL(c *check.C, tableName string, cols []*model.Column) (string, []interface{}) {
   356  	var sb strings.Builder
   357  	_, err := sb.WriteString("SELECT count(1) FROM " + tableName + " WHERE ")
   358  	c.Assert(err, check.IsNil)
   359  	params := make([]interface{}, 0, len(cols))
   360  	for i, col := range cols {
   361  		if col == nil {
   362  			continue
   363  		}
   364  		if i != 0 {
   365  			_, err = sb.WriteString(" AND ")
   366  			c.Assert(err, check.IsNil)
   367  		}
   368  		if col.Value == nil {
   369  			_, err = sb.WriteString(col.Name + " IS NULL")
   370  			c.Assert(err, check.IsNil)
   371  			continue
   372  		}
   373  		params = append(params, col.Value)
   374  		if col.Type == mysql.TypeJSON {
   375  			_, err = sb.WriteString(col.Name + " = CAST(? AS JSON)")
   376  		} else {
   377  			_, err = sb.WriteString(col.Name + " = ?")
   378  		}
   379  		c.Assert(err, check.IsNil)
   380  	}
   381  	return sb.String(), params
   382  }
   383  
   384  func walkTableSpanInStore(c *check.C, store tidbkv.Storage, tableID int64, f func(key []byte, value []byte)) {
   385  	txn, err := store.Begin()
   386  	c.Assert(err, check.IsNil)
   387  	defer txn.Rollback() //nolint:errcheck
   388  	tableSpan := regionspan.GetTableSpan(tableID)
   389  	kvIter, err := txn.Iter(tableSpan.Start, tableSpan.End)
   390  	c.Assert(err, check.IsNil)
   391  	defer kvIter.Close()
   392  	for kvIter.Valid() {
   393  		f(kvIter.Key(), kvIter.Value())
   394  		err = kvIter.Next()
   395  		c.Assert(err, check.IsNil)
   396  	}
   397  }