github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sqlmodel/causality_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  	"sync"
    18  	"testing"
    19  
    20  	cdcmodel "github.com/pingcap/tiflow/cdc/model"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestCausalityKeys(t *testing.T) {
    25  	t.Parallel()
    26  
    27  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
    28  
    29  	cases := []struct {
    30  		createSQL string
    31  		preValue  []interface{}
    32  		postValue []interface{}
    33  
    34  		causalityKeys []string
    35  	}{
    36  		{
    37  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT, c3 VARCHAR(10) UNIQUE)",
    38  			[]interface{}{1, 2, "abc"},
    39  			[]interface{}{3, 4, "abc"},
    40  			[]string{"abc.c3.db.tb1", "1.c.db.tb1", "abc.c3.db.tb1", "3.c.db.tb1"},
    41  		},
    42  		{
    43  			"CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT, c3 VARCHAR(10), UNIQUE INDEX(c3(1)))",
    44  			[]interface{}{1, 2, "abc"},
    45  			[]interface{}{3, 4, "adef"},
    46  			[]string{"a.c3.db.tb1", "1.c.db.tb1", "a.c3.db.tb1", "3.c.db.tb1"},
    47  		},
    48  
    49  		// test not string key
    50  		{
    51  			"CREATE TABLE tb1 (a INT, b INT, UNIQUE KEY a(a))",
    52  			[]interface{}{100, 200},
    53  			nil,
    54  			[]string{"100.a.db.tb1"},
    55  		},
    56  
    57  		// test text
    58  		{
    59  			"CREATE TABLE tb1 (a INT, b TEXT, UNIQUE KEY b(b(3)))",
    60  			[]interface{}{1, "1234"},
    61  			nil,
    62  			[]string{"123.b.db.tb1"},
    63  		},
    64  
    65  		// test composite keys
    66  		{
    67  			"CREATE TABLE tb1 (a INT, b TEXT, UNIQUE KEY c2(a, b(3)))",
    68  			[]interface{}{1, "1234"},
    69  			nil,
    70  			[]string{"1.a.123.b.db.tb1"},
    71  		},
    72  
    73  		// test value is null
    74  		{
    75  			"CREATE TABLE tb1 (a INT, b TEXT, UNIQUE KEY c2(a, b(3)))",
    76  			[]interface{}{1, nil},
    77  			nil,
    78  			[]string{"1.a.db.tb1"},
    79  		},
    80  	}
    81  
    82  	for _, ca := range cases {
    83  		ti := mockTableInfo(t, ca.createSQL)
    84  		change := NewRowChange(source, nil, ca.preValue, ca.postValue, ti, nil, nil)
    85  		require.Equal(t, ca.causalityKeys, change.CausalityKeys())
    86  	}
    87  }
    88  
    89  func TestCausalityKeysNoRace(t *testing.T) {
    90  	t.Parallel()
    91  
    92  	source := &cdcmodel.TableName{Schema: "db", Table: "tb1"}
    93  	ti := mockTableInfo(t, "CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT, c3 VARCHAR(10) UNIQUE)")
    94  	var wg sync.WaitGroup
    95  	for i := 0; i < 100; i++ {
    96  		wg.Add(1)
    97  		go func() {
    98  			change := NewRowChange(source, nil, []interface{}{1, 2, "abc"}, []interface{}{3, 4, "abc"}, ti, nil, nil)
    99  			change.CausalityKeys()
   100  			wg.Done()
   101  		}()
   102  	}
   103  	wg.Wait()
   104  }
   105  
   106  func TestGetCausalityString(t *testing.T) {
   107  	t.Parallel()
   108  
   109  	source := &cdcmodel.TableName{Schema: "db", Table: "tbl"}
   110  
   111  	testCases := []struct {
   112  		schema string
   113  		values []interface{}
   114  		keys   []string
   115  	}{
   116  		{
   117  			// test no keys will use full row data instead of table name
   118  			schema: `create table t1(a int)`,
   119  			values: []interface{}{10},
   120  			keys:   []string{"10.a.db.tbl"},
   121  		},
   122  		{
   123  			// one primary key
   124  			schema: `create table t2(a int primary key, b double)`,
   125  			values: []interface{}{60, 70.5},
   126  			keys:   []string{"60.a.db.tbl"},
   127  		},
   128  		{
   129  			// one unique key
   130  			schema: `create table t3(a int unique, b double)`,
   131  			values: []interface{}{60, 70.5},
   132  			keys:   []string{"60.a.db.tbl"},
   133  		},
   134  		{
   135  			// one ordinary key
   136  			schema: `create table t4(a int, b double, key(b))`,
   137  			values: []interface{}{60, 70.5},
   138  			keys:   []string{"60.a.70.5.b.db.tbl"},
   139  		},
   140  		{
   141  			// multiple keys
   142  			schema: `create table t5(a int, b text, c int, key(a), key(b(3)))`,
   143  			values: []interface{}{13, "abcdef", 15},
   144  			keys:   []string{"13.a.abcdef.b.15.c.db.tbl"},
   145  		},
   146  		{
   147  			// multiple keys with primary key
   148  			schema: `create table t6(a int primary key, b varchar(16) unique)`,
   149  			values: []interface{}{16, "xyz"},
   150  			keys:   []string{"xyz.b.db.tbl", "16.a.db.tbl"},
   151  		},
   152  		{
   153  			// non-integer primary key
   154  			schema: `create table t65(a int unique, b varchar(16) primary key)`,
   155  			values: []interface{}{16, "xyz"},
   156  			keys:   []string{"16.a.db.tbl", "xyz.b.db.tbl"},
   157  		},
   158  		{
   159  			// case insensitive
   160  			schema: `create table t_ci(a int unique, b varchar(16) primary key)default charset=utf8 collate=utf8_unicode_ci`,
   161  			values: []interface{}{16, "XyZ"},
   162  			keys:   []string{"16.a.db.tbl", "xyz.b.db.tbl"},
   163  		},
   164  		{
   165  			// case sensitive
   166  			schema: `create table t_bin(a int unique, b varchar(16) primary key)default charset=utf8 collate=utf8_bin`,
   167  			values: []interface{}{16, "XyZ"},
   168  			keys:   []string{"16.a.db.tbl", "XyZ.b.db.tbl"},
   169  		},
   170  		{
   171  			// primary key of multiple columns
   172  			schema: `create table t7(a int, b int, primary key(a, b))`,
   173  			values: []interface{}{59, 69},
   174  			keys:   []string{"59.a.69.b.db.tbl"},
   175  		},
   176  		{
   177  			// ordinary key of multiple columns
   178  			schema: `create table t75(a int, b int, c int, key(a, b), key(c, b))`,
   179  			values: []interface{}{48, 58, 68},
   180  			keys:   []string{"48.a.58.b.68.c.db.tbl"},
   181  		},
   182  		{
   183  			// so many keys
   184  			schema: `
   185  				create table t8(
   186  					a int, b int, c int,
   187  					primary key(a, b),
   188  					unique key(b, c),
   189  					key(a, b, c),
   190  					unique key(c, a)
   191  				)
   192  			`,
   193  			values: []interface{}{27, 37, 47},
   194  			keys:   []string{"27.a.37.b.db.tbl", "37.b.47.c.db.tbl", "47.c.27.a.db.tbl"},
   195  		},
   196  		{
   197  			// `null` for unique key
   198  			schema: `
   199  				create table t8(
   200  					a int, b int default null,
   201  					primary key(a),
   202  					unique key(b)
   203  				)
   204  			`,
   205  			values: []interface{}{17, nil},
   206  			keys:   []string{"17.a.db.tbl"},
   207  		},
   208  	}
   209  
   210  	for _, ca := range testCases {
   211  		ti := mockTableInfo(t, ca.schema)
   212  		change := NewRowChange(source, nil, nil, ca.values, ti, nil, nil)
   213  		change.lazyInitWhereHandle()
   214  		require.Equal(t, ca.keys, change.getCausalityString(ca.values))
   215  	}
   216  }