github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sqlmodel/row_change_test.go (about)

     1  // Copyright 2022 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 sqlmodel
    15  
    16  import (
    17  	"testing"
    18  
    19  	"github.com/pingcap/tidb/pkg/ddl"
    20  	"github.com/pingcap/tidb/pkg/parser"
    21  	"github.com/pingcap/tidb/pkg/parser/ast"
    22  	"github.com/pingcap/tidb/pkg/parser/charset"
    23  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    24  	_ "github.com/pingcap/tidb/pkg/planner/core" // init expression.EvalSimpleAst related function
    25  	timock "github.com/pingcap/tidb/pkg/util/mock"
    26  	cdcmodel "github.com/pingcap/tiflow/cdc/model"
    27  	"github.com/pingcap/tiflow/dm/pkg/log"
    28  	"github.com/pingcap/tiflow/dm/pkg/utils"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/stretchr/testify/suite"
    31  )
    32  
    33  func mockTableInfo(t *testing.T, sql string) *timodel.TableInfo {
    34  	p := parser.New()
    35  	se := timock.NewContext()
    36  	node, err := p.ParseOneStmt(sql, "", "")
    37  	require.NoError(t, err)
    38  	dbChs, dbColl := charset.GetDefaultCharsetAndCollate()
    39  	ti, err := ddl.BuildTableInfoWithStmt(se, node.(*ast.CreateTableStmt), dbChs, dbColl, nil)
    40  	require.NoError(t, err)
    41  	return ti
    42  }
    43  
    44  type dpanicSuite struct {
    45  	suite.Suite
    46  }
    47  
    48  func (s *dpanicSuite) SetupSuite() {
    49  	err := log.InitLogger(&log.Config{Level: "debug"})
    50  	s.NoError(err)
    51  }
    52  
    53  func TestDpanicSuite(t *testing.T) {
    54  	suite.Run(t, new(dpanicSuite))
    55  }
    56  
    57  func TestNewRowChange(t *testing.T) {
    58  	t.Parallel()
    59  
    60  	source := &cdcmodel.TableName{Schema: "db", Table: "tbl"}
    61  	target := &cdcmodel.TableName{Schema: "db", Table: "tbl_routed"}
    62  	sourceTI := mockTableInfo(t, "CREATE TABLE tbl (id INT PRIMARY KEY, name INT)")
    63  	targetTI := mockTableInfo(t, "CREATE TABLE tbl_routed (id INT PRIMARY KEY, name INT)")
    64  	tiSession := utils.NewSessionCtx(map[string]string{
    65  		"time_zone": "+08:00",
    66  	})
    67  
    68  	expected := &RowChange{
    69  		sourceTable:     source,
    70  		targetTable:     target,
    71  		preValues:       []interface{}{1, 2},
    72  		postValues:      []interface{}{1, 3},
    73  		sourceTableInfo: sourceTI,
    74  		targetTableInfo: targetTI,
    75  		tiSessionCtx:    tiSession,
    76  		tp:              RowChangeUpdate,
    77  		whereHandle:     nil,
    78  	}
    79  
    80  	actual := NewRowChange(source, target, []interface{}{1, 2}, []interface{}{1, 3}, sourceTI, targetTI, tiSession)
    81  	require.Equal(t, expected, actual)
    82  
    83  	actual.lazyInitWhereHandle()
    84  	require.NotNil(t, actual.whereHandle)
    85  
    86  	// test some arguments of NewRowChange can be nil
    87  
    88  	expected.targetTable = expected.sourceTable
    89  	expected.targetTableInfo = expected.sourceTableInfo
    90  	expected.tiSessionCtx = utils.ZeroSessionCtx
    91  	expected.whereHandle = nil
    92  	actual = NewRowChange(source, nil, []interface{}{1, 2}, []interface{}{1, 3}, sourceTI, nil, nil)
    93  	require.Equal(t, expected, actual)
    94  }
    95  
    96  func (s *dpanicSuite) TestRowChangeType() {
    97  	change := &RowChange{preValues: []interface{}{1}}
    98  	change.calculateType()
    99  	s.Equal(RowChangeDelete, change.tp)
   100  	change = &RowChange{preValues: []interface{}{1}, postValues: []interface{}{2}}
   101  	change.calculateType()
   102  	s.Equal(RowChangeUpdate, change.tp)
   103  	change = &RowChange{postValues: []interface{}{1}}
   104  	change.calculateType()
   105  	s.Equal(RowChangeInsert, change.tp)
   106  
   107  	s.Panics(func() {
   108  		change = &RowChange{}
   109  		change.calculateType()
   110  	})
   111  }
   112  
   113  func (s *dpanicSuite) TestGenDelete() {
   114  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
   115  	target := &cdcmodel.TableName{Schema: "db", Table: "tb2"}
   116  
   117  	cases := []struct {
   118  		sourceCreateSQL string
   119  		targetCreateSQL string
   120  		preValues       []interface{}
   121  
   122  		expectedSQL  string
   123  		expectedArgs []interface{}
   124  	}{
   125  		{
   126  			"CREATE TABLE tb1 (id INT PRIMARY KEY, name INT)",
   127  			"CREATE TABLE tb2 (id INT PRIMARY KEY, name INT, extra VARCHAR(20))",
   128  			[]interface{}{1, 2},
   129  
   130  			"DELETE FROM `db`.`tb2` WHERE `id` = ? LIMIT 1",
   131  			[]interface{}{1},
   132  		},
   133  		{
   134  			"CREATE TABLE tb1 (c INT, c2 INT UNIQUE)",
   135  			"CREATE TABLE tb2 (c INT, c2 INT UNIQUE)",
   136  			[]interface{}{1, 2},
   137  
   138  			"DELETE FROM `db`.`tb2` WHERE `c2` = ? LIMIT 1",
   139  			[]interface{}{2},
   140  		},
   141  		// next 2 cases test NULL value
   142  		{
   143  			"CREATE TABLE tb1 (c INT, c2 INT UNIQUE)",
   144  			"CREATE TABLE tb2 (c INT, c2 INT UNIQUE)",
   145  			[]interface{}{1, nil},
   146  
   147  			"DELETE FROM `db`.`tb2` WHERE `c` = ? AND `c2` IS ? LIMIT 1",
   148  			[]interface{}{1, nil},
   149  		},
   150  		{
   151  			"CREATE TABLE tb1 (c INT, c2 INT)",
   152  			"CREATE TABLE tb2 (c INT, c2 INT)",
   153  			[]interface{}{1, nil},
   154  
   155  			"DELETE FROM `db`.`tb2` WHERE `c` = ? AND `c2` IS ? LIMIT 1",
   156  			[]interface{}{1, nil},
   157  		},
   158  		// next 2 cases test using downstream table to generate WHERE
   159  		{
   160  			"CREATE TABLE tb1 (id INT PRIMARY KEY, user_id INT NOT NULL UNIQUE)",
   161  			"CREATE TABLE tb2 (new_id INT PRIMARY KEY, id INT, user_id INT NOT NULL UNIQUE)",
   162  			[]interface{}{1, 2},
   163  
   164  			"DELETE FROM `db`.`tb2` WHERE `user_id` = ? LIMIT 1",
   165  			[]interface{}{2},
   166  		},
   167  		{
   168  			"CREATE TABLE tb1 (id INT PRIMARY KEY, c2 INT)",
   169  			"CREATE TABLE tb2 (new_id INT PRIMARY KEY, id INT, c2 INT)",
   170  			[]interface{}{1, 2},
   171  
   172  			"DELETE FROM `db`.`tb2` WHERE `id` = ? AND `c2` = ? LIMIT 1",
   173  			[]interface{}{1, 2},
   174  		},
   175  	}
   176  
   177  	for _, c := range cases {
   178  		sourceTI := mockTableInfo(s.T(), c.sourceCreateSQL)
   179  		targetTI := mockTableInfo(s.T(), c.targetCreateSQL)
   180  		change := NewRowChange(source, target, c.preValues, nil, sourceTI, targetTI, nil)
   181  		sql, args := change.GenSQL(DMLDelete)
   182  		s.Equal(c.expectedSQL, sql)
   183  		s.Equal(c.expectedArgs, args)
   184  	}
   185  
   186  	// a RowChangeUpdate can still generate DELETE SQL
   187  	sourceTI := mockTableInfo(s.T(), "CREATE TABLE tb1 (id INT PRIMARY KEY, name INT)")
   188  	change := NewRowChange(source, nil, []interface{}{1, 2}, []interface{}{3, 4}, sourceTI, nil, nil)
   189  	sql, args := change.GenSQL(DMLDelete)
   190  	s.Equal("DELETE FROM `db`.`tb1` WHERE `id` = ? LIMIT 1", sql)
   191  	s.Equal([]interface{}{1}, args)
   192  
   193  	change = NewRowChange(source, nil, nil, []interface{}{3, 4}, sourceTI, nil, nil)
   194  	s.Panics(func() {
   195  		change.GenSQL(DMLDelete)
   196  	})
   197  }
   198  
   199  func (s *dpanicSuite) TestGenUpdate() {
   200  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
   201  	target := &cdcmodel.TableName{Schema: "db", Table: "tb2"}
   202  
   203  	cases := []struct {
   204  		sourceCreateSQL string
   205  		targetCreateSQL string
   206  		preValues       []interface{}
   207  		postValues      []interface{}
   208  
   209  		expectedSQL  string
   210  		expectedArgs []interface{}
   211  	}{
   212  		{
   213  			"CREATE TABLE tb1 (id INT PRIMARY KEY, name INT)",
   214  			"CREATE TABLE tb2 (id INT PRIMARY KEY, name INT, extra VARCHAR(20))",
   215  			[]interface{}{1, 2},
   216  			[]interface{}{3, 4},
   217  
   218  			"UPDATE `db`.`tb2` SET `id` = ?, `name` = ? WHERE `id` = ? LIMIT 1",
   219  			[]interface{}{3, 4, 1},
   220  		},
   221  		{
   222  			"CREATE TABLE tb1 (id INT UNIQUE, name INT)",
   223  			"CREATE TABLE tb2 (id INT UNIQUE, name INT)",
   224  			[]interface{}{nil, 2},
   225  			[]interface{}{3, 4},
   226  
   227  			"UPDATE `db`.`tb2` SET `id` = ?, `name` = ? WHERE `id` IS ? AND `name` = ? LIMIT 1",
   228  			[]interface{}{3, 4, nil, 2},
   229  		},
   230  		{
   231  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT)",
   232  			"CREATE TABLE tb2 (c INT, c2 INT)",
   233  			[]interface{}{1, 2},
   234  			[]interface{}{3, 4},
   235  
   236  			"UPDATE `db`.`tb2` SET `c` = ?, `c2` = ? WHERE `c` = ? AND `c2` = ? LIMIT 1",
   237  			[]interface{}{3, 4, 1, 2},
   238  		},
   239  		// next 2 cases test generated column
   240  		{
   241  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   242  			"CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   243  			[]interface{}{1, 2},
   244  			[]interface{}{3, 4},
   245  
   246  			"UPDATE `db`.`tb2` SET `c` = ? WHERE `c` = ? LIMIT 1",
   247  			[]interface{}{3, 1},
   248  		},
   249  		{
   250  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   251  			"CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT)",
   252  			[]interface{}{1, 2},
   253  			[]interface{}{3, 4},
   254  
   255  			"UPDATE `db`.`tb2` SET `c` = ?, `c2` = ? WHERE `c` = ? LIMIT 1",
   256  			[]interface{}{3, 4, 1},
   257  		},
   258  	}
   259  
   260  	for _, c := range cases {
   261  		sourceTI := mockTableInfo(s.T(), c.sourceCreateSQL)
   262  		targetTI := mockTableInfo(s.T(), c.targetCreateSQL)
   263  		change := NewRowChange(source, target, c.preValues, c.postValues, sourceTI, targetTI, nil)
   264  		sql, args := change.GenSQL(DMLUpdate)
   265  		s.Equal(c.expectedSQL, sql)
   266  		s.Equal(c.expectedArgs, args)
   267  	}
   268  
   269  	sourceTI := mockTableInfo(s.T(), "CREATE TABLE tb1 (id INT PRIMARY KEY, name INT)")
   270  	change := NewRowChange(source, nil, nil, []interface{}{3, 4}, sourceTI, nil, nil)
   271  	s.Panics(func() {
   272  		change.GenSQL(DMLUpdate)
   273  	})
   274  }
   275  
   276  func (s *dpanicSuite) TestExpressionIndex() {
   277  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
   278  	sql := `CREATE TABLE tb1 (
   279      	id INT PRIMARY KEY,
   280      	j JSON,
   281      	UNIQUE KEY j_index ((cast(json_extract(j,'$[*]') as signed array)), id)
   282  )`
   283  	ti := mockTableInfo(s.T(), sql)
   284  	change := NewRowChange(source, nil, nil, []interface{}{1, `[1,2,3]`}, ti, nil, nil)
   285  	sql, args := change.GenSQL(DMLInsert)
   286  	s.Equal("INSERT INTO `db`.`tb1` (`id`,`j`) VALUES (?,?)", sql)
   287  	s.Equal([]interface{}{1, `[1,2,3]`}, args)
   288  	require.Equal(s.T(), 2, change.ColumnCount())
   289  	keys := change.CausalityKeys()
   290  	// TODO: need change it after future fix
   291  	require.Equal(s.T(), []string{"1.id.db.tb1"}, keys)
   292  
   293  	change2 := NewRowChange(source, nil, []interface{}{1, `[1,2,3]`}, []interface{}{1, `[1,2,3,4]`}, ti, nil, nil)
   294  	sql, args = change2.GenSQL(DMLUpdate)
   295  	s.Equal("UPDATE `db`.`tb1` SET `id` = ?, `j` = ? WHERE `id` = ? LIMIT 1", sql)
   296  	s.Equal([]interface{}{1, `[1,2,3,4]`, 1}, args)
   297  
   298  	change2.Reduce(change)
   299  	sql, args = change2.GenSQL(DMLInsert)
   300  	s.Equal("INSERT INTO `db`.`tb1` (`id`,`j`) VALUES (?,?)", sql)
   301  	s.Equal([]interface{}{1, `[1,2,3,4]`}, args)
   302  }
   303  
   304  func TestGenInsert(t *testing.T) {
   305  	t.Parallel()
   306  
   307  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
   308  	target := &cdcmodel.TableName{Schema: "db", Table: "tb2"}
   309  
   310  	cases := []struct {
   311  		sourceCreateSQL string
   312  		targetCreateSQL string
   313  		postValues      []interface{}
   314  
   315  		expectedInsertSQL      string
   316  		expectedReplaceSQL     string
   317  		expectedInsertOnDupSQL string
   318  		expectedArgs           []interface{}
   319  	}{
   320  		{
   321  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT)",
   322  			"CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT, extra VARCHAR(20))",
   323  			[]interface{}{1, 2},
   324  
   325  			"INSERT INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?)",
   326  			"REPLACE INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?)",
   327  			"INSERT INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?) ON DUPLICATE KEY UPDATE `c`=VALUES(`c`),`c2`=VALUES(`c2`)",
   328  			[]interface{}{1, 2},
   329  		},
   330  		// next 2 cases test generated column
   331  		{
   332  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   333  			"CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   334  			[]interface{}{1, 2},
   335  
   336  			"INSERT INTO `db`.`tb2` (`c`) VALUES (?)",
   337  			"REPLACE INTO `db`.`tb2` (`c`) VALUES (?)",
   338  			"INSERT INTO `db`.`tb2` (`c`) VALUES (?) ON DUPLICATE KEY UPDATE `c`=VALUES(`c`)",
   339  			[]interface{}{1},
   340  		},
   341  		{
   342  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT AS (c+1))",
   343  			"CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT)",
   344  			[]interface{}{1, 2},
   345  
   346  			"INSERT INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?)",
   347  			"REPLACE INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?)",
   348  			"INSERT INTO `db`.`tb2` (`c`,`c2`) VALUES (?,?) ON DUPLICATE KEY UPDATE `c`=VALUES(`c`),`c2`=VALUES(`c2`)",
   349  			[]interface{}{1, 2},
   350  		},
   351  	}
   352  
   353  	for _, c := range cases {
   354  		sourceTI := mockTableInfo(t, c.sourceCreateSQL)
   355  		targetTI := mockTableInfo(t, c.targetCreateSQL)
   356  		change := NewRowChange(source, target, nil, c.postValues, sourceTI, targetTI, nil)
   357  		sql, args := change.GenSQL(DMLInsert)
   358  		require.Equal(t, c.expectedInsertSQL, sql)
   359  		require.Equal(t, c.expectedArgs, args)
   360  		sql, args = change.GenSQL(DMLReplace)
   361  		require.Equal(t, c.expectedReplaceSQL, sql)
   362  		require.Equal(t, c.expectedArgs, args)
   363  		sql, args = change.GenSQL(DMLInsertOnDuplicateUpdate)
   364  		require.Equal(t, c.expectedInsertOnDupSQL, sql)
   365  		require.Equal(t, c.expectedArgs, args)
   366  	}
   367  }