vitess.io/vitess@v0.16.2/go/vt/schema/online_ddl_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package schema 18 19 import ( 20 "encoding/hex" 21 "fmt" 22 "strings" 23 "testing" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 28 "vitess.io/vitess/go/vt/sqlparser" 29 ) 30 31 func TestCreateUUID(t *testing.T) { 32 _, err := CreateUUIDWithDelimiter("_") 33 assert.NoError(t, err) 34 } 35 36 func TestIsOnlineDDLUUID(t *testing.T) { 37 for i := 0; i < 20; i++ { 38 uuid, err := CreateOnlineDDLUUID() 39 assert.NoError(t, err) 40 assert.True(t, IsOnlineDDLUUID(uuid)) 41 } 42 tt := []string{ 43 "a0638f6b_ec7b_11ea_9bf8_000d3a9b8a9a_", // suffix invalid 44 "_a0638f6b_ec7b_11ea_9bf8_000d3a9b8a9a", // prefix invalid 45 "a0638f6b_ec7b_11ea_9bf8_000d3a9b8a9z", // "z" character invalid 46 "a0638f6b-ec7b-11ea-9bf8-000d3a9b8a9a", // dash invalid 47 "a0638f6b_ec7b_11ea_9bf8_000d3a9b8a9", // too short 48 } 49 for _, tc := range tt { 50 assert.False(t, IsOnlineDDLUUID(tc)) 51 } 52 } 53 54 func TestGetGCUUID(t *testing.T) { 55 uuids := map[string]bool{} 56 count := 20 57 for i := 0; i < count; i++ { 58 onlineDDL, err := NewOnlineDDL("ks", "tbl", "alter table t drop column c", NewDDLStrategySetting(DDLStrategyDirect, ""), "", "") 59 assert.NoError(t, err) 60 gcUUID := onlineDDL.GetGCUUID() 61 assert.True(t, IsGCUUID(gcUUID)) 62 uuids[gcUUID] = true 63 } 64 assert.Equal(t, count, len(uuids)) 65 } 66 func TestGetActionStr(t *testing.T) { 67 tt := []struct { 68 statement string 69 actionStr string 70 isError bool 71 }{ 72 { 73 statement: "create table t (id int primary key)", 74 actionStr: sqlparser.CreateStr, 75 }, 76 { 77 statement: "alter table t drop column c", 78 actionStr: sqlparser.AlterStr, 79 }, 80 { 81 statement: "drop table t", 82 actionStr: sqlparser.DropStr, 83 }, 84 { 85 statement: "rename table t to t2", 86 isError: true, 87 }, 88 } 89 for _, ts := range tt { 90 t.Run(ts.statement, func(t *testing.T) { 91 onlineDDL := &OnlineDDL{SQL: ts.statement} 92 _, actionStr, err := onlineDDL.GetActionStr() 93 if ts.isError { 94 assert.Error(t, err) 95 } else { 96 assert.NoError(t, err) 97 assert.Equal(t, actionStr, ts.actionStr) 98 } 99 }) 100 } 101 } 102 103 func TestIsOnlineDDLTableName(t *testing.T) { 104 names := []string{ 105 "_4e5dcf80_354b_11eb_82cd_f875a4d24e90_20201203114014_gho", 106 "_4e5dcf80_354b_11eb_82cd_f875a4d24e90_20201203114014_ghc", 107 "_4e5dcf80_354b_11eb_82cd_f875a4d24e90_20201203114014_del", 108 "_4e5dcf80_354b_11eb_82cd_f875a4d24e90_20201203114013_new", 109 "_84371a37_6153_11eb_9917_f875a4d24e90_20210128122816_vrepl", 110 "_table_old", 111 "__table_old", 112 } 113 for _, tableName := range names { 114 assert.True(t, IsOnlineDDLTableName(tableName)) 115 } 116 irrelevantNames := []string{ 117 "t", 118 "_table_new", 119 "__table_new", 120 "_table_gho", 121 "_table_ghc", 122 "_table_del", 123 "_table_vrepl", 124 "table_old", 125 } 126 for _, tableName := range irrelevantNames { 127 assert.False(t, IsOnlineDDLTableName(tableName)) 128 } 129 } 130 131 func TestGetRevertUUID(t *testing.T) { 132 tt := []struct { 133 statement string 134 uuid string 135 isError bool 136 }{ 137 { 138 statement: "revert 4e5dcf80_354b_11eb_82cd_f875a4d24e90", 139 uuid: "4e5dcf80_354b_11eb_82cd_f875a4d24e90", 140 }, 141 { 142 statement: "REVERT 4e5dcf80_354b_11eb_82cd_f875a4d24e90", 143 uuid: "4e5dcf80_354b_11eb_82cd_f875a4d24e90", 144 }, 145 { 146 statement: "alter table t drop column c", 147 isError: true, 148 }, 149 } 150 for _, ts := range tt { 151 t.Run(ts.statement, func(t *testing.T) { 152 onlineDDL := &OnlineDDL{SQL: ts.statement} 153 uuid, err := onlineDDL.GetRevertUUID() 154 if ts.isError { 155 assert.Error(t, err) 156 return 157 } 158 assert.NoError(t, err) 159 assert.Equal(t, ts.uuid, uuid) 160 }) 161 } 162 migrationContext := "354b-11eb-82cd-f875a4d24e90" 163 for _, ts := range tt { 164 t.Run(ts.statement, func(t *testing.T) { 165 onlineDDL, err := NewOnlineDDL("test_ks", "t", ts.statement, NewDDLStrategySetting(DDLStrategyOnline, ""), migrationContext, "") 166 assert.NoError(t, err) 167 require.NotNil(t, onlineDDL) 168 uuid, err := onlineDDL.GetRevertUUID() 169 if ts.isError { 170 assert.Error(t, err) 171 return 172 } 173 assert.NoError(t, err) 174 assert.Equal(t, ts.uuid, uuid) 175 }) 176 } 177 } 178 179 func TestNewOnlineDDL(t *testing.T) { 180 migrationContext := "354b-11eb-82cd-f875a4d24e90" 181 tt := []struct { 182 sql string 183 isError bool 184 }{ 185 { 186 sql: "drop table t", 187 }, 188 { 189 sql: "create table t (id int primary key)", 190 }, 191 { 192 sql: "alter table t engine=innodb", 193 }, 194 { 195 sql: "revert 4e5dcf80_354b_11eb_82cd_f875a4d24e90", // legacy syntax; kept one release version for backwards compatibility. Can remove after v11.0 is released 196 }, 197 { 198 sql: "revert vitess_migration '4e5dcf80_354b_11eb_82cd_f875a4d24e90'", 199 }, 200 { 201 sql: "alter vitess_migration '4e5dcf80_354b_11eb_82cd_f875a4d24e90' cancel", 202 isError: true, 203 }, 204 { 205 sql: "select id from t", 206 isError: true, 207 }, 208 } 209 strategies := []*DDLStrategySetting{ 210 NewDDLStrategySetting(DDLStrategyDirect, ""), 211 NewDDLStrategySetting(DDLStrategyVitess, ""), 212 NewDDLStrategySetting(DDLStrategyOnline, "-singleton"), 213 } 214 215 for _, ts := range tt { 216 t.Run(ts.sql, func(t *testing.T) { 217 for _, stgy := range strategies { 218 t.Run(stgy.ToString(), func(t *testing.T) { 219 onlineDDL, err := NewOnlineDDL("test_ks", "t", ts.sql, stgy, migrationContext, "") 220 if ts.isError { 221 assert.Error(t, err) 222 return 223 } 224 assert.NoError(t, err) 225 // onlineDDL.SQL enriched with /*vt+ ... */ comment 226 assert.Contains(t, onlineDDL.SQL, hex.EncodeToString([]byte(onlineDDL.UUID))) 227 assert.Contains(t, onlineDDL.SQL, hex.EncodeToString([]byte(migrationContext))) 228 assert.Contains(t, onlineDDL.SQL, hex.EncodeToString([]byte(string(stgy.Strategy)))) 229 }) 230 } 231 }) 232 } 233 234 t.Run("explicit UUID", func(t *testing.T) { 235 var err error 236 var onlineDDL *OnlineDDL 237 238 onlineDDL, err = NewOnlineDDL("test_ks", "t", "alter table t engine=innodb", NewDDLStrategySetting(DDLStrategyVitess, ""), migrationContext, "") 239 assert.NoError(t, err) 240 assert.True(t, IsOnlineDDLUUID(onlineDDL.UUID)) 241 242 _, err = NewOnlineDDL("test_ks", "t", "alter table t engine=innodb", NewDDLStrategySetting(DDLStrategyOnline, ""), migrationContext, "abc") 243 assert.Error(t, err) 244 245 onlineDDL, err = NewOnlineDDL("test_ks", "t", "alter table t engine=innodb", NewDDLStrategySetting(DDLStrategyVitess, ""), migrationContext, "4e5dcf80_354b_11eb_82cd_f875a4d24e90") 246 assert.NoError(t, err) 247 assert.Equal(t, "4e5dcf80_354b_11eb_82cd_f875a4d24e90", onlineDDL.UUID) 248 249 _, err = NewOnlineDDL("test_ks", "t", "alter table t engine=innodb", NewDDLStrategySetting(DDLStrategyVitess, ""), migrationContext, " 4e5dcf80_354b_11eb_82cd_f875a4d24e90") 250 assert.Error(t, err) 251 }) 252 } 253 254 func TestNewOnlineDDLs(t *testing.T) { 255 type expect struct { 256 sqls []string 257 notDDL bool 258 parseError bool 259 isError bool 260 expectErrorText string 261 isView bool 262 } 263 tests := map[string]expect{ 264 "alter table t add column i int, drop column d": {sqls: []string{"alter table t add column i int, drop column d"}}, 265 "create table t (id int primary key)": {sqls: []string{"create table t (id int primary key)"}}, 266 "drop table t": {sqls: []string{"drop table t"}}, 267 "drop table if exists t": {sqls: []string{"drop table if exists t"}}, 268 "drop table t1, t2, t3": {sqls: []string{"drop table t1", "drop table t2", "drop table t3"}}, 269 "drop table if exists t1, t2, t3": {sqls: []string{"drop table if exists t1", "drop table if exists t2", "drop table if exists t3"}}, 270 "create index i_idx on t(id)": {sqls: []string{"alter table t add index i_idx (id)"}}, 271 "create index i_idx on t(name(12))": {sqls: []string{"alter table t add index i_idx (`name`(12))"}}, 272 "create index i_idx on t(id, `ts`, name(12))": {sqls: []string{"alter table t add index i_idx (id, ts, `name`(12))"}}, 273 "create unique index i_idx on t(id)": {sqls: []string{"alter table t add unique index i_idx (id)"}}, 274 "create index i_idx using btree on t(id)": {sqls: []string{"alter table t add index i_idx (id) using btree"}}, 275 "create view v as select * from t": {sqls: []string{"create view v as select * from t"}, isView: true}, 276 "alter view v as select * from t": {sqls: []string{"alter view v as select * from t"}, isView: true}, 277 "drop view v": {sqls: []string{"drop view v"}, isView: true}, 278 "drop view if exists v": {sqls: []string{"drop view if exists v"}, isView: true}, 279 "create index with syntax error i_idx on t(id)": {parseError: true}, 280 "select * from t": {notDDL: true}, 281 "drop database t": {notDDL: true}, 282 "truncate table t": {isError: true}, 283 "rename table t to t1": {isError: true}, 284 "alter table corder add FOREIGN KEY my_fk(customer_id) reference customer(customer_id)": {isError: true, expectErrorText: "syntax error"}, 285 "alter table corder add FOREIGN KEY my_fk(customer_id) references customer(customer_id)": {isError: true, expectErrorText: "foreign key constraints are not supported"}, 286 "alter table corder rename as something_else": {isError: true, expectErrorText: "RENAME is not supported in online DDL"}, 287 "CREATE TABLE if not exists t (id bigint unsigned NOT NULL AUTO_INCREMENT, ts datetime(6) DEFAULT NULL, error_column NO_SUCH_TYPE NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB": {isError: true, expectErrorText: "near"}, 288 } 289 migrationContext := "354b-11eb-82cd-f875a4d24e90" 290 for query, expect := range tests { 291 t.Run(query, func(t *testing.T) { 292 stmt, err := sqlparser.Parse(query) 293 if expect.parseError { 294 assert.Error(t, err) 295 return 296 } 297 assert.NoError(t, err) 298 ddlStmt, ok := stmt.(sqlparser.DDLStatement) 299 if expect.notDDL { 300 assert.False(t, ok) 301 return 302 } 303 assert.True(t, ok) 304 305 onlineDDLs, err := NewOnlineDDLs("test_ks", query, ddlStmt, NewDDLStrategySetting(DDLStrategyVitess, ""), migrationContext, "") 306 if expect.isError { 307 assert.Error(t, err) 308 assert.Contains(t, err.Error(), expect.expectErrorText) 309 return 310 } 311 assert.NoError(t, err) 312 313 sqls := []string{} 314 for _, onlineDDL := range onlineDDLs { 315 sql, err := onlineDDL.sqlWithoutComments() 316 assert.NoError(t, err) 317 sql = strings.ReplaceAll(sql, "\n", "") 318 sql = strings.ReplaceAll(sql, "\t", "") 319 sqls = append(sqls, sql) 320 assert.Equal(t, expect.isView, onlineDDL.IsView()) 321 } 322 assert.Equal(t, expect.sqls, sqls) 323 }) 324 } 325 } 326 327 func TestNewOnlineDDLsForeignKeys(t *testing.T) { 328 type expect struct { 329 sqls []string 330 notDDL bool 331 parseError bool 332 isError bool 333 expectErrorText string 334 isView bool 335 } 336 queries := []string{ 337 "alter table corder add FOREIGN KEY my_fk(customer_id) references customer(customer_id)", 338 "create table t1 (id int primary key, i int, foreign key (i) references parent(id))", 339 } 340 341 migrationContext := "354b-11eb-82cd-f875a4d24e90" 342 for _, query := range queries { 343 t.Run(query, func(t *testing.T) { 344 for _, allowForeignKeys := range []bool{false, true} { 345 testName := fmt.Sprintf("%t", allowForeignKeys) 346 t.Run(testName, func(t *testing.T) { 347 stmt, err := sqlparser.Parse(query) 348 require.NoError(t, err) 349 ddlStmt, ok := stmt.(sqlparser.DDLStatement) 350 require.True(t, ok) 351 352 flags := "" 353 if allowForeignKeys { 354 flags = "--unsafe-allow-foreign-keys" 355 } 356 onlineDDLs, err := NewOnlineDDLs("test_ks", query, ddlStmt, NewDDLStrategySetting(DDLStrategyVitess, flags), migrationContext, "") 357 if allowForeignKeys { 358 assert.NoError(t, err) 359 } else { 360 assert.Error(t, err) 361 assert.Contains(t, err.Error(), "foreign key constraints are not supported") 362 } 363 364 for _, onlineDDL := range onlineDDLs { 365 sql, err := onlineDDL.sqlWithoutComments() 366 assert.NoError(t, err) 367 assert.NotEmpty(t, sql) 368 } 369 }) 370 } 371 }) 372 } 373 } 374 375 func TestOnlineDDLFromCommentedStatement(t *testing.T) { 376 queries := []string{ 377 `create table t (id int primary key)`, 378 `alter table t drop primary key`, 379 `drop table if exists t`, 380 `create view v as select * from t`, 381 `drop view v`, 382 `alter view v as select * from t`, 383 `revert vitess_migration '4e5dcf80_354b_11eb_82cd_f875a4d24e90'`, 384 } 385 strategySetting := NewDDLStrategySetting(DDLStrategyGhost, `-singleton -declarative --max-load="Threads_running=5"`) 386 migrationContext := "354b-11eb-82cd-f875a4d24e90" 387 for _, query := range queries { 388 t.Run(query, func(t *testing.T) { 389 o1, err := NewOnlineDDL("ks", "t", query, strategySetting, migrationContext, "") 390 require.NoError(t, err) 391 392 stmt, err := sqlparser.Parse(o1.SQL) 393 require.NoError(t, err) 394 395 o2, err := OnlineDDLFromCommentedStatement(stmt) 396 require.NoError(t, err) 397 assert.True(t, IsOnlineDDLUUID(o2.UUID)) 398 assert.Equal(t, o1.UUID, o2.UUID) 399 assert.Equal(t, migrationContext, o2.MigrationContext) 400 assert.Equal(t, "t", o2.Table) 401 assert.Equal(t, strategySetting.Strategy, o2.Strategy) 402 assert.Equal(t, strategySetting.Options, o2.Options) 403 }) 404 } 405 }