github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sqlmodel/multirow_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 cdcmodel "github.com/pingcap/tiflow/cdc/model" 20 "github.com/stretchr/testify/require" 21 ) 22 23 type genSQLFunc func(changes ...*RowChange) (string, []interface{}) 24 25 func TestGenDeleteMultiRows(t *testing.T) { 26 t.Parallel() 27 28 source1 := &cdcmodel.TableName{Schema: "db", Table: "tb1"} 29 source2 := &cdcmodel.TableName{Schema: "db", Table: "tb2"} 30 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 31 32 sourceTI1 := mockTableInfo(t, "CREATE TABLE tb1 (c INT PRIMARY KEY, c2 INT)") 33 sourceTI2 := mockTableInfo(t, "CREATE TABLE tb2 (c INT PRIMARY KEY, c2 INT)") 34 targetTI := mockTableInfo(t, "CREATE TABLE tb (c INT PRIMARY KEY, c2 INT)") 35 36 change1 := NewRowChange(source1, target, []interface{}{1, 2}, nil, sourceTI1, targetTI, nil) 37 change2 := NewRowChange(source2, target, []interface{}{3, 4}, nil, sourceTI2, targetTI, nil) 38 sql, args := GenDeleteSQL(change1, change2) 39 40 require.Equal(t, "DELETE FROM `db`.`tb` WHERE (`c` = ?) OR (`c` = ?)", sql) 41 require.Equal(t, []interface{}{1, 3}, args) 42 } 43 44 func TestGenUpdateMultiRows(t *testing.T) { 45 t.Parallel() 46 testGenUpdateMultiRows(t, GenUpdateSQL) 47 } 48 49 func TestGenUpdateMultiRowsOneColPK(t *testing.T) { 50 t.Parallel() 51 testGenUpdateMultiRowsOneColPK(t, GenUpdateSQL) 52 } 53 54 func TestGenUpdateMultiRowsWithVirtualGeneratedColumn(t *testing.T) { 55 t.Parallel() 56 testGenUpdateMultiRowsWithVirtualGeneratedColumn(t, GenUpdateSQL) 57 testGenUpdateMultiRowsWithVirtualGeneratedColumns(t, GenUpdateSQL) 58 } 59 60 func TestGenUpdateMultiRowsWithStoredGeneratedColumn(t *testing.T) { 61 t.Parallel() 62 testGenUpdateMultiRowsWithStoredGeneratedColumn(t, GenUpdateSQL) 63 } 64 65 func testGenUpdateMultiRows(t *testing.T, genUpdate genSQLFunc) { 66 source1 := &cdcmodel.TableName{Schema: "db", Table: "tb1"} 67 source2 := &cdcmodel.TableName{Schema: "db", Table: "tb2"} 68 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 69 70 sourceTI1 := mockTableInfo(t, "CREATE TABLE tb1 (c INT, c2 INT, c3 INT, UNIQUE KEY (c, c2))") 71 sourceTI2 := mockTableInfo(t, "CREATE TABLE tb2 (c INT, c2 INT, c3 INT, UNIQUE KEY (c, c2))") 72 targetTI := mockTableInfo(t, "CREATE TABLE tb (c INT, c2 INT, c3 INT, UNIQUE KEY (c, c2))") 73 74 change1 := NewRowChange(source1, target, []interface{}{1, 2, 3}, []interface{}{10, 20, 30}, sourceTI1, targetTI, nil) 75 change2 := NewRowChange(source2, target, []interface{}{4, 5, 6}, []interface{}{40, 50, 60}, sourceTI2, targetTI, nil) 76 sql, args := genUpdate(change1, change2) 77 78 expectedSQL := "UPDATE `db`.`tb` SET " + 79 "`c`=CASE WHEN `c` = ? AND `c2` = ? THEN ? WHEN `c` = ? AND `c2` = ? THEN ? END, " + 80 "`c2`=CASE WHEN `c` = ? AND `c2` = ? THEN ? WHEN `c` = ? AND `c2` = ? THEN ? END, " + 81 "`c3`=CASE WHEN `c` = ? AND `c2` = ? THEN ? WHEN `c` = ? AND `c2` = ? THEN ? END " + 82 "WHERE (`c` = ? AND `c2` = ?) OR (`c` = ? AND `c2` = ?)" 83 expectedArgs := []interface{}{ 84 1, 2, 10, 4, 5, 40, 85 1, 2, 20, 4, 5, 50, 86 1, 2, 30, 4, 5, 60, 87 1, 2, 4, 5, 88 } 89 90 require.Equal(t, expectedSQL, sql) 91 require.Equal(t, expectedArgs, args) 92 } 93 94 func testGenUpdateMultiRowsOneColPK(t *testing.T, genUpdate genSQLFunc) { 95 source1 := &cdcmodel.TableName{Schema: "db", Table: "tb1"} 96 source2 := &cdcmodel.TableName{Schema: "db", Table: "tb2"} 97 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 98 99 sourceTI1 := mockTableInfo(t, "CREATE TABLE tb1 (c INT, c2 INT, c3 INT, PRIMARY KEY (c))") 100 sourceTI2 := mockTableInfo(t, "CREATE TABLE tb2 (c INT, c2 INT, c3 INT, PRIMARY KEY (c))") 101 targetTI := mockTableInfo(t, "CREATE TABLE tb (c INT, c2 INT, c3 INT, PRIMARY KEY (c))") 102 103 change1 := NewRowChange(source1, target, []interface{}{1, 2, 3}, []interface{}{10, 20, 30}, sourceTI1, targetTI, nil) 104 change2 := NewRowChange(source2, target, []interface{}{4, 5, 6}, []interface{}{40, 50, 60}, sourceTI2, targetTI, nil) 105 sql, args := genUpdate(change1, change2) 106 107 expectedSQL := "UPDATE `db`.`tb` SET " + 108 "`c`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END, " + 109 "`c2`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END, " + 110 "`c3`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END " + 111 "WHERE (`c` = ?) OR (`c` = ?)" 112 expectedArgs := []interface{}{ 113 1, 10, 4, 40, 114 1, 20, 4, 50, 115 1, 30, 4, 60, 116 1, 4, 117 } 118 119 require.Equal(t, expectedSQL, sql) 120 require.Equal(t, expectedArgs, args) 121 } 122 123 func testGenUpdateMultiRowsWithVirtualGeneratedColumn(t *testing.T, genUpdate genSQLFunc) { 124 source := &cdcmodel.TableName{Schema: "db", Table: "tb"} 125 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 126 127 sourceTI := mockTableInfo(t, "CREATE TABLE tb1 (c INT, c1 int as (c+100) virtual not null, c2 INT, c3 INT, PRIMARY KEY (c))") 128 targetTI := mockTableInfo(t, "CREATE TABLE tb (c INT, c1 int as (c+100) virtual not null, c2 INT, c3 INT, PRIMARY KEY (c))") 129 130 change1 := NewRowChange(source, target, []interface{}{1, 101, 2, 3}, []interface{}{10, 110, 20, 30}, sourceTI, targetTI, nil) 131 change2 := NewRowChange(source, target, []interface{}{4, 104, 5, 6}, []interface{}{40, 140, 50, 60}, sourceTI, targetTI, nil) 132 change3 := NewRowChange(source, target, []interface{}{7, 107, 8, 9}, []interface{}{70, 170, 80, 90}, sourceTI, targetTI, nil) 133 sql, args := genUpdate(change1, change2, change3) 134 135 expectedSQL := "UPDATE `db`.`tb` SET " + 136 "`c`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END, " + 137 "`c2`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END, " + 138 "`c3`=CASE WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? WHEN `c` = ? THEN ? END " + 139 "WHERE (`c` = ?) OR (`c` = ?) OR (`c` = ?)" 140 expectedArgs := []interface{}{ 141 1, 10, 4, 40, 7, 70, 142 1, 20, 4, 50, 7, 80, 143 1, 30, 4, 60, 7, 90, 144 1, 4, 7, 145 } 146 147 require.Equal(t, expectedSQL, sql) 148 require.Equal(t, expectedArgs, args) 149 } 150 151 // multiple generated columns test case 152 func testGenUpdateMultiRowsWithVirtualGeneratedColumns(t *testing.T, genUpdate genSQLFunc) { 153 source := &cdcmodel.TableName{Schema: "db", Table: "tb"} 154 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 155 156 sourceTI := mockTableInfo(t, `CREATE TABLE tb1 (c0 int as (c4*c4) virtual not null, 157 c1 int as (c+100) virtual not null, c2 INT, c3 INT, c4 INT, PRIMARY KEY (c4))`) 158 targetTI := mockTableInfo(t, `CREATE TABLE tb (c0 int as (c4*c4) virtual not null, 159 c1 int as (c+100) virtual not null, c2 INT, c3 INT, c4 INT, PRIMARY KEY (c4))`) 160 161 change1 := NewRowChange(source, target, []interface{}{1, 101, 2, 3, 1}, []interface{}{100, 110, 20, 30, 10}, sourceTI, targetTI, nil) 162 change2 := NewRowChange(source, target, []interface{}{16, 104, 5, 6, 4}, []interface{}{1600, 140, 50, 60, 40}, sourceTI, targetTI, nil) 163 change3 := NewRowChange(source, target, []interface{}{49, 107, 8, 9, 7}, []interface{}{4900, 170, 80, 90, 70}, sourceTI, targetTI, nil) 164 sql, args := genUpdate(change1, change2, change3) 165 166 expectedSQL := "UPDATE `db`.`tb` SET " + 167 "`c2`=CASE WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? END, " + 168 "`c3`=CASE WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? END, " + 169 "`c4`=CASE WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? WHEN `c4` = ? THEN ? END " + 170 "WHERE (`c4` = ?) OR (`c4` = ?) OR (`c4` = ?)" 171 expectedArgs := []interface{}{ 172 1, 20, 4, 50, 7, 80, 173 1, 30, 4, 60, 7, 90, 174 1, 10, 4, 40, 7, 70, 175 1, 4, 7, 176 } 177 178 require.Equal(t, expectedSQL, sql) 179 require.Equal(t, expectedArgs, args) 180 } 181 182 func testGenUpdateMultiRowsWithStoredGeneratedColumn(t *testing.T, genUpdate genSQLFunc) { 183 source := &cdcmodel.TableName{Schema: "db", Table: "tb"} 184 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 185 186 sourceTI := mockTableInfo(t, "CREATE TABLE tb1 (c INT, c1 int as (c+100) stored, c2 INT, c3 INT, PRIMARY KEY (c1))") 187 targetTI := mockTableInfo(t, "CREATE TABLE tb (c INT, c1 int as (c+100) stored, c2 INT, c3 INT, PRIMARY KEY (c1))") 188 189 change1 := NewRowChange(source, target, []interface{}{1, 101, 2, 3}, []interface{}{10, 110, 20, 30}, sourceTI, targetTI, nil) 190 change2 := NewRowChange(source, target, []interface{}{4, 104, 5, 6}, []interface{}{40, 140, 50, 60}, sourceTI, targetTI, nil) 191 change3 := NewRowChange(source, target, []interface{}{7, 107, 8, 9}, []interface{}{70, 170, 80, 90}, sourceTI, targetTI, nil) 192 sql, args := genUpdate(change1, change2, change3) 193 194 expectedSQL := "UPDATE `db`.`tb` SET " + 195 "`c`=CASE WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? END, " + 196 "`c2`=CASE WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? END, " + 197 "`c3`=CASE WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? WHEN `c1` = ? THEN ? END " + 198 "WHERE (`c1` = ?) OR (`c1` = ?) OR (`c1` = ?)" 199 expectedArgs := []interface{}{ 200 101, 10, 104, 40, 107, 70, 201 101, 20, 104, 50, 107, 80, 202 101, 30, 104, 60, 107, 90, 203 101, 104, 107, 204 } 205 206 require.Equal(t, expectedSQL, sql) 207 require.Equal(t, expectedArgs, args) 208 } 209 210 func TestGenInsertMultiRows(t *testing.T) { 211 t.Parallel() 212 213 source1 := &cdcmodel.TableName{Schema: "db", Table: "tb1"} 214 source2 := &cdcmodel.TableName{Schema: "db", Table: "tb2"} 215 target := &cdcmodel.TableName{Schema: "db", Table: "tb"} 216 217 sourceTI1 := mockTableInfo(t, "CREATE TABLE tb1 (gen INT AS (c+1), c INT PRIMARY KEY, c2 INT)") 218 sourceTI2 := mockTableInfo(t, "CREATE TABLE tb2 (gen INT AS (c+1), c INT PRIMARY KEY, c2 INT)") 219 targetTI := mockTableInfo(t, "CREATE TABLE tb (gen INT AS (c+1), c INT PRIMARY KEY, c2 INT)") 220 221 change1 := NewRowChange(source1, target, nil, []interface{}{2, 1, 2}, sourceTI1, targetTI, nil) 222 change2 := NewRowChange(source2, target, nil, []interface{}{4, 3, 4}, sourceTI2, targetTI, nil) 223 224 sql, args := GenInsertSQL(DMLInsert, change1, change2) 225 require.Equal(t, "INSERT INTO `db`.`tb` (`c`,`c2`) VALUES (?,?),(?,?)", sql) 226 require.Equal(t, []interface{}{1, 2, 3, 4}, args) 227 228 sql, args = GenInsertSQL(DMLReplace, change1, change2) 229 require.Equal(t, "REPLACE INTO `db`.`tb` (`c`,`c2`) VALUES (?,?),(?,?)", sql) 230 require.Equal(t, []interface{}{1, 2, 3, 4}, args) 231 232 sql, args = GenInsertSQL(DMLInsertOnDuplicateUpdate, change1, change2) 233 require.Equal(t, "INSERT INTO `db`.`tb` (`c`,`c2`) VALUES (?,?),(?,?) ON DUPLICATE KEY UPDATE `c`=VALUES(`c`),`c2`=VALUES(`c2`)", sql) 234 require.Equal(t, []interface{}{1, 2, 3, 4}, args) 235 }