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  }