vitess.io/vitess@v0.16.2/go/vt/schemadiff/diff_test.go (about)

     1  /*
     2  Copyright 2022 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 schemadiff
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"vitess.io/vitess/go/vt/sqlparser"
    26  )
    27  
    28  func TestDiffTables(t *testing.T) {
    29  	tt := []struct {
    30  		name     string
    31  		from     string
    32  		to       string
    33  		diff     string
    34  		cdiff    string
    35  		fromName string
    36  		toName   string
    37  		action   string
    38  		isError  bool
    39  	}{
    40  		{
    41  			name: "identical",
    42  			from: "create table t(id int primary key)",
    43  			to:   "create table t(id int primary key)",
    44  		},
    45  		{
    46  			name:     "change of columns",
    47  			from:     "create table t(id int primary key)",
    48  			to:       "create table t(id int primary key, i int)",
    49  			diff:     "alter table t add column i int",
    50  			cdiff:    "ALTER TABLE `t` ADD COLUMN `i` int",
    51  			action:   "alter",
    52  			fromName: "t",
    53  			toName:   "t",
    54  		},
    55  		{
    56  			name:   "create",
    57  			to:     "create table t(id int primary key)",
    58  			diff:   "create table t (\n\tid int,\n\tprimary key (id)\n)",
    59  			cdiff:  "CREATE TABLE `t` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
    60  			action: "create",
    61  			toName: "t",
    62  		},
    63  		{
    64  			name:     "drop",
    65  			from:     "create table t(id int primary key)",
    66  			diff:     "drop table t",
    67  			cdiff:    "DROP TABLE `t`",
    68  			action:   "drop",
    69  			fromName: "t",
    70  		},
    71  		{
    72  			name: "none",
    73  		},
    74  	}
    75  	hints := &DiffHints{}
    76  	for _, ts := range tt {
    77  		t.Run(ts.name, func(t *testing.T) {
    78  			var fromCreateTable *sqlparser.CreateTable
    79  			if ts.from != "" {
    80  				fromStmt, err := sqlparser.ParseStrictDDL(ts.from)
    81  				assert.NoError(t, err)
    82  				var ok bool
    83  				fromCreateTable, ok = fromStmt.(*sqlparser.CreateTable)
    84  				assert.True(t, ok)
    85  			}
    86  			var toCreateTable *sqlparser.CreateTable
    87  			if ts.to != "" {
    88  				toStmt, err := sqlparser.ParseStrictDDL(ts.to)
    89  				assert.NoError(t, err)
    90  				var ok bool
    91  				toCreateTable, ok = toStmt.(*sqlparser.CreateTable)
    92  				assert.True(t, ok)
    93  			}
    94  			// Testing two paths:
    95  			// - one, just diff the "CREATE TABLE..." strings
    96  			// - two, diff the CreateTable constructs
    97  			// Technically, DiffCreateTablesQueries calls DiffTables,
    98  			// but we expose both to users of this library. so we want to make sure
    99  			// both work as expected irrespective of any relationship between them.
   100  			dq, dqerr := DiffCreateTablesQueries(ts.from, ts.to, hints)
   101  			d, err := DiffTables(fromCreateTable, toCreateTable, hints)
   102  			switch {
   103  			case ts.isError:
   104  				assert.Error(t, err)
   105  				assert.Error(t, dqerr)
   106  			case ts.diff == "":
   107  				assert.NoError(t, err)
   108  				assert.NoError(t, dqerr)
   109  				assert.Nil(t, d)
   110  				assert.Nil(t, dq)
   111  			default:
   112  				assert.NoError(t, err)
   113  				require.NotNil(t, d)
   114  				require.False(t, d.IsEmpty())
   115  				{
   116  					diff := d.StatementString()
   117  					assert.Equal(t, ts.diff, diff)
   118  					action, err := DDLActionStr(d)
   119  					assert.NoError(t, err)
   120  					assert.Equal(t, ts.action, action)
   121  
   122  					// validate we can parse back the statement
   123  					_, err = sqlparser.ParseStrictDDL(diff)
   124  					assert.NoError(t, err)
   125  
   126  					eFrom, eTo := d.Entities()
   127  					if ts.fromName != "" {
   128  						assert.Equal(t, ts.fromName, eFrom.Name())
   129  					}
   130  					if ts.toName != "" {
   131  						assert.Equal(t, ts.toName, eTo.Name())
   132  					}
   133  				}
   134  				{
   135  					canonicalDiff := d.CanonicalStatementString()
   136  					assert.Equal(t, ts.cdiff, canonicalDiff)
   137  					action, err := DDLActionStr(d)
   138  					assert.NoError(t, err)
   139  					assert.Equal(t, ts.action, action)
   140  
   141  					// validate we can parse back the statement
   142  					_, err = sqlparser.ParseStrictDDL(canonicalDiff)
   143  					assert.NoError(t, err)
   144  				}
   145  				// let's also check dq, and also validate that dq's statement is identical to d's
   146  				assert.NoError(t, dqerr)
   147  				require.NotNil(t, dq)
   148  				require.False(t, dq.IsEmpty())
   149  				diff := dq.StatementString()
   150  				assert.Equal(t, ts.diff, diff)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  func TestDiffViews(t *testing.T) {
   157  	tt := []struct {
   158  		name     string
   159  		from     string
   160  		to       string
   161  		diff     string
   162  		cdiff    string
   163  		fromName string
   164  		toName   string
   165  		action   string
   166  		isError  bool
   167  	}{
   168  		{
   169  			name: "identical",
   170  			from: "create view v1 as select a, b, c from t",
   171  			to:   "create view v1 as select a, b, c from t",
   172  		},
   173  		{
   174  			name:     "change of column list, qualifiers",
   175  			from:     "create view v1 (col1, `col2`, `col3`) as select `a`, `b`, c from t",
   176  			to:       "create view v1 (`col1`, col2, colother) as select a, b, `c` from t",
   177  			diff:     "alter view v1(col1, col2, colother) as select a, b, c from t",
   178  			cdiff:    "ALTER VIEW `v1`(`col1`, `col2`, `colother`) AS SELECT `a`, `b`, `c` FROM `t`",
   179  			action:   "alter",
   180  			fromName: "v1",
   181  			toName:   "v1",
   182  		},
   183  		{
   184  			name:   "create",
   185  			to:     "create view v1 as select a, b, c from t",
   186  			diff:   "create view v1 as select a, b, c from t",
   187  			cdiff:  "CREATE VIEW `v1` AS SELECT `a`, `b`, `c` FROM `t`",
   188  			action: "create",
   189  			toName: "v1",
   190  		},
   191  		{
   192  			name:     "drop",
   193  			from:     "create view v1 as select a, b, c from t",
   194  			diff:     "drop view v1",
   195  			cdiff:    "DROP VIEW `v1`",
   196  			action:   "drop",
   197  			fromName: "v1",
   198  		},
   199  		{
   200  			name: "none",
   201  		},
   202  	}
   203  	hints := &DiffHints{}
   204  	for _, ts := range tt {
   205  		t.Run(ts.name, func(t *testing.T) {
   206  			var fromCreateView *sqlparser.CreateView
   207  			if ts.from != "" {
   208  				fromStmt, err := sqlparser.ParseStrictDDL(ts.from)
   209  				assert.NoError(t, err)
   210  				var ok bool
   211  				fromCreateView, ok = fromStmt.(*sqlparser.CreateView)
   212  				assert.True(t, ok)
   213  			}
   214  			var toCreateView *sqlparser.CreateView
   215  			if ts.to != "" {
   216  				toStmt, err := sqlparser.ParseStrictDDL(ts.to)
   217  				assert.NoError(t, err)
   218  				var ok bool
   219  				toCreateView, ok = toStmt.(*sqlparser.CreateView)
   220  				assert.True(t, ok)
   221  			}
   222  			// Testing two paths:
   223  			// - one, just diff the "CREATE TABLE..." strings
   224  			// - two, diff the CreateTable constructs
   225  			// Technically, DiffCreateTablesQueries calls DiffTables,
   226  			// but we expose both to users of this library. so we want to make sure
   227  			// both work as expected irrespective of any relationship between them.
   228  			dq, dqerr := DiffCreateViewsQueries(ts.from, ts.to, hints)
   229  			d, err := DiffViews(fromCreateView, toCreateView, hints)
   230  			switch {
   231  			case ts.isError:
   232  				assert.Error(t, err)
   233  				assert.Error(t, dqerr)
   234  			case ts.diff == "":
   235  				assert.NoError(t, err)
   236  				assert.NoError(t, dqerr)
   237  				assert.Nil(t, d)
   238  				assert.Nil(t, dq)
   239  			default:
   240  				assert.NoError(t, err)
   241  				require.NotNil(t, d)
   242  				require.False(t, d.IsEmpty())
   243  				{
   244  					diff := d.StatementString()
   245  					assert.Equal(t, ts.diff, diff)
   246  					action, err := DDLActionStr(d)
   247  					assert.NoError(t, err)
   248  					assert.Equal(t, ts.action, action)
   249  
   250  					// validate we can parse back the statement
   251  					_, err = sqlparser.ParseStrictDDL(diff)
   252  					assert.NoError(t, err)
   253  
   254  					eFrom, eTo := d.Entities()
   255  					if ts.fromName != "" {
   256  						assert.Equal(t, ts.fromName, eFrom.Name())
   257  					}
   258  					if ts.toName != "" {
   259  						assert.Equal(t, ts.toName, eTo.Name())
   260  					}
   261  				}
   262  				{
   263  					canonicalDiff := d.CanonicalStatementString()
   264  					assert.Equal(t, ts.cdiff, canonicalDiff)
   265  					action, err := DDLActionStr(d)
   266  					assert.NoError(t, err)
   267  					assert.Equal(t, ts.action, action)
   268  
   269  					// validate we can parse back the statement
   270  					_, err = sqlparser.ParseStrictDDL(canonicalDiff)
   271  					assert.NoError(t, err)
   272  				}
   273  
   274  				// let's also check dq, and also validate that dq's statement is identical to d's
   275  				assert.NoError(t, dqerr)
   276  				require.NotNil(t, dq)
   277  				require.False(t, dq.IsEmpty())
   278  				diff := dq.StatementString()
   279  				assert.Equal(t, ts.diff, diff)
   280  			}
   281  		})
   282  	}
   283  }
   284  
   285  func TestDiffSchemas(t *testing.T) {
   286  	tt := []struct {
   287  		name        string
   288  		from        string
   289  		to          string
   290  		diffs       []string
   291  		cdiffs      []string
   292  		expectError string
   293  		tableRename int
   294  	}{
   295  		{
   296  			name: "identical tables",
   297  			from: "create table t(id int primary key)",
   298  			to:   "create table t(id int primary key)",
   299  		},
   300  		{
   301  			name: "change of table column",
   302  			from: "create table t(id int primary key, v varchar(10))",
   303  			to:   "create table t(id int primary key, v varchar(20))",
   304  			diffs: []string{
   305  				"alter table t modify column v varchar(20)",
   306  			},
   307  			cdiffs: []string{
   308  				"ALTER TABLE `t` MODIFY COLUMN `v` varchar(20)",
   309  			},
   310  		},
   311  		{
   312  			name: "change of table column tinyint 1 to longer",
   313  			from: "create table t(id int primary key, i tinyint(1))",
   314  			to:   "create table t(id int primary key, i tinyint(2))",
   315  			diffs: []string{
   316  				"alter table t modify column i tinyint",
   317  			},
   318  			cdiffs: []string{
   319  				"ALTER TABLE `t` MODIFY COLUMN `i` tinyint",
   320  			},
   321  		},
   322  		{
   323  			name: "change of table column tinyint 2 to 1",
   324  			from: "create table t(id int primary key, i tinyint(2))",
   325  			to:   "create table t(id int primary key, i tinyint(1))",
   326  			diffs: []string{
   327  				"alter table t modify column i tinyint(1)",
   328  			},
   329  			cdiffs: []string{
   330  				"ALTER TABLE `t` MODIFY COLUMN `i` tinyint(1)",
   331  			},
   332  		},
   333  		{
   334  			name: "change of table columns, added",
   335  			from: "create table t(id int primary key)",
   336  			to:   "create table t(id int primary key, i int)",
   337  			diffs: []string{
   338  				"alter table t add column i int",
   339  			},
   340  			cdiffs: []string{
   341  				"ALTER TABLE `t` ADD COLUMN `i` int",
   342  			},
   343  		},
   344  		{
   345  			name: "change with function",
   346  			from: "create table identifiers (id binary(16) NOT NULL DEFAULT (uuid_to_bin(uuid(),true)))",
   347  			to:   "create table identifiers (company_id mediumint unsigned NOT NULL, id binary(16) NOT NULL DEFAULT (uuid_to_bin(uuid(),true)))",
   348  			diffs: []string{
   349  				"alter table identifiers add column company_id mediumint unsigned not null first",
   350  			},
   351  			cdiffs: []string{
   352  				"ALTER TABLE `identifiers` ADD COLUMN `company_id` mediumint unsigned NOT NULL FIRST",
   353  			},
   354  		},
   355  		{
   356  			name: "change within functional index",
   357  			from: "create table t1 (id mediumint unsigned NOT NULL, deleted_at timestamp, primary key (id), unique key deleted_check (id, (if((deleted_at is null),0,NULL))))",
   358  			to:   "create table t1 (id mediumint unsigned NOT NULL, deleted_at timestamp, primary key (id), unique key deleted_check (id, (if((deleted_at is not null),0,NULL))))",
   359  			diffs: []string{
   360  				"alter table t1 drop key deleted_check, add unique key deleted_check (id, (if(deleted_at is not null, 0, null)))",
   361  			},
   362  			cdiffs: []string{
   363  				"ALTER TABLE `t1` DROP KEY `deleted_check`, ADD UNIQUE KEY `deleted_check` (`id`, (if(`deleted_at` IS NOT NULL, 0, NULL)))",
   364  			},
   365  		},
   366  		{
   367  			name: "change for a check",
   368  			from: "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `Check1` CHECK ((`test` >= 0)))",
   369  			to:   "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `RenamedCheck1` CHECK ((`test` >= 0)))",
   370  			diffs: []string{
   371  				"alter table t drop check Check1, add constraint RenamedCheck1 check (test >= 0)",
   372  			},
   373  			cdiffs: []string{
   374  				"ALTER TABLE `t` DROP CHECK `Check1`, ADD CONSTRAINT `RenamedCheck1` CHECK (`test` >= 0)",
   375  			},
   376  		},
   377  		{
   378  			name: "not enforce a check",
   379  			from: "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `Check1` CHECK ((`test` >= 0)))",
   380  			to:   "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `Check1` CHECK ((`test` >= 0)) NOT ENFORCED)",
   381  			diffs: []string{
   382  				"alter table t alter check Check1 not enforced",
   383  			},
   384  			cdiffs: []string{
   385  				"ALTER TABLE `t` ALTER CHECK `Check1` NOT ENFORCED",
   386  			},
   387  		},
   388  		{
   389  			name: "enforce a check",
   390  			from: "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `Check1` CHECK ((`test` >= 0)) NOT ENFORCED)",
   391  			to:   "CREATE TABLE `t` (`id` int NOT NULL, `test` int NOT NULL DEFAULT '0', PRIMARY KEY (`id`), CONSTRAINT `Check1` CHECK ((`test` >= 0)))",
   392  			diffs: []string{
   393  				"alter table t alter check Check1 enforced",
   394  			},
   395  			cdiffs: []string{
   396  				"ALTER TABLE `t` ALTER CHECK `Check1` ENFORCED",
   397  			},
   398  		},
   399  		{
   400  			name: "change of table columns, removed",
   401  			from: "create table t(id int primary key, i int)",
   402  			to:   "create table t(id int primary key)",
   403  			diffs: []string{
   404  				"alter table t drop column i",
   405  			},
   406  			cdiffs: []string{
   407  				"ALTER TABLE `t` DROP COLUMN `i`",
   408  			},
   409  		},
   410  		{
   411  			name: "create table",
   412  			to:   "create table t(id int primary key)",
   413  			diffs: []string{
   414  				"create table t (\n\tid int,\n\tprimary key (id)\n)",
   415  			},
   416  			cdiffs: []string{
   417  				"CREATE TABLE `t` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
   418  			},
   419  		},
   420  		{
   421  			name: "create table 2",
   422  			from: ";;; ; ;    ;;;",
   423  			to:   "create table t(id int primary key)",
   424  			diffs: []string{
   425  				"create table t (\n\tid int,\n\tprimary key (id)\n)",
   426  			},
   427  			cdiffs: []string{
   428  				"CREATE TABLE `t` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
   429  			},
   430  		},
   431  		{
   432  			name: "drop table",
   433  			from: "create table t(id int primary key)",
   434  			diffs: []string{
   435  				"drop table t",
   436  			},
   437  			cdiffs: []string{
   438  				"DROP TABLE `t`",
   439  			},
   440  		},
   441  		{
   442  			name: "create, alter, drop tables",
   443  			from: "create table t1(id int primary key); create table t2(id int primary key); create table t3(id int primary key)",
   444  			to:   "create table t4(id int primary key); create table t2(id bigint primary key); create table t3(id int primary key)",
   445  			diffs: []string{
   446  				"drop table t1",
   447  				"alter table t2 modify column id bigint",
   448  				"create table t4 (\n\tid int,\n\tprimary key (id)\n)",
   449  			},
   450  			cdiffs: []string{
   451  				"DROP TABLE `t1`",
   452  				"ALTER TABLE `t2` MODIFY COLUMN `id` bigint",
   453  				"CREATE TABLE `t4` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
   454  			},
   455  		},
   456  		{
   457  			name: "identical tables: drop and create",
   458  			from: "create table t1(id int primary key); create table t2(id int unsigned primary key);",
   459  			to:   "create table t1(id int primary key); create table t3(id int unsigned primary key);",
   460  			diffs: []string{
   461  				"drop table t2",
   462  				"create table t3 (\n\tid int unsigned,\n\tprimary key (id)\n)",
   463  			},
   464  			cdiffs: []string{
   465  				"DROP TABLE `t2`",
   466  				"CREATE TABLE `t3` (\n\t`id` int unsigned,\n\tPRIMARY KEY (`id`)\n)",
   467  			},
   468  		},
   469  		{
   470  			name: "identical tables: heuristic rename",
   471  			from: "create table t1(id int primary key); create table t2a(id int unsigned primary key);",
   472  			to:   "create table t1(id int primary key); create table t2b(id int unsigned primary key);",
   473  			diffs: []string{
   474  				"rename table t2a to t2b",
   475  			},
   476  			cdiffs: []string{
   477  				"RENAME TABLE `t2a` TO `t2b`",
   478  			},
   479  			tableRename: TableRenameHeuristicStatement,
   480  		},
   481  		{
   482  			name: "identical tables: drop and create",
   483  			from: "create table t1a(id int primary key); create table t2a(id int unsigned primary key); create table t3a(id smallint primary key); ",
   484  			to:   "create table t1b(id bigint primary key); create table t2b(id int unsigned primary key); create table t3b(id int primary key); ",
   485  			diffs: []string{
   486  				"drop table t1a",
   487  				"drop table t2a",
   488  				"drop table t3a",
   489  				"create table t1b (\n\tid bigint,\n\tprimary key (id)\n)",
   490  				"create table t2b (\n\tid int unsigned,\n\tprimary key (id)\n)",
   491  				"create table t3b (\n\tid int,\n\tprimary key (id)\n)",
   492  			},
   493  			cdiffs: []string{
   494  				"DROP TABLE `t1a`",
   495  				"DROP TABLE `t2a`",
   496  				"DROP TABLE `t3a`",
   497  				"CREATE TABLE `t1b` (\n\t`id` bigint,\n\tPRIMARY KEY (`id`)\n)",
   498  				"CREATE TABLE `t2b` (\n\t`id` int unsigned,\n\tPRIMARY KEY (`id`)\n)",
   499  				"CREATE TABLE `t3b` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
   500  			},
   501  		},
   502  		{
   503  			name: "identical tables: multiple heuristic rename",
   504  			from: "create table t1a(id int primary key); create table t2a(id int unsigned primary key); create table t3a(id smallint primary key); ",
   505  			to:   "create table t1b(id bigint primary key); create table t2b(id int unsigned primary key); create table t3b(id int primary key); ",
   506  			diffs: []string{
   507  				"drop table t3a",
   508  				"create table t1b (\n\tid bigint,\n\tprimary key (id)\n)",
   509  				"rename table t1a to t3b",
   510  				"rename table t2a to t2b",
   511  			},
   512  			cdiffs: []string{
   513  				"DROP TABLE `t3a`",
   514  				"CREATE TABLE `t1b` (\n\t`id` bigint,\n\tPRIMARY KEY (`id`)\n)",
   515  				"RENAME TABLE `t1a` TO `t3b`",
   516  				"RENAME TABLE `t2a` TO `t2b`",
   517  			},
   518  			tableRename: TableRenameHeuristicStatement,
   519  		},
   520  		// Views
   521  		{
   522  			name: "identical views",
   523  			from: "create table t(id int); create view v1 as select * from t",
   524  			to:   "create table t(id int); create view v1 as select * from t",
   525  		},
   526  		{
   527  			name: "modified view",
   528  			from: "create table t(id int); create view v1 as select * from t",
   529  			to:   "create table t(id int); create view v1 as select id from t",
   530  			diffs: []string{
   531  				"alter view v1 as select id from t",
   532  			},
   533  			cdiffs: []string{
   534  				"ALTER VIEW `v1` AS SELECT `id` FROM `t`",
   535  			},
   536  		},
   537  		{
   538  			name: "drop view",
   539  			from: "create table t(id int); create view v1 as select * from t",
   540  			to:   "create table t(id int);",
   541  			diffs: []string{
   542  				"drop view v1",
   543  			},
   544  			cdiffs: []string{
   545  				"DROP VIEW `v1`",
   546  			},
   547  		},
   548  		{
   549  			name: "create view",
   550  			from: "create table t(id int)",
   551  			to:   "create table t(id int); create view v1 as select id from t",
   552  			diffs: []string{
   553  				"create view v1 as select id from t",
   554  			},
   555  			cdiffs: []string{
   556  				"CREATE VIEW `v1` AS SELECT `id` FROM `t`",
   557  			},
   558  		},
   559  		{
   560  			name:        "create view: unresolved dependencies",
   561  			from:        "create table t(id int)",
   562  			to:          "create table t(id int); create view v1 as select id from t2",
   563  			expectError: (&ViewDependencyUnresolvedError{View: "v1"}).Error(),
   564  		},
   565  		{
   566  			name: "convert table to view",
   567  			from: "create table t(id int); create table v1 (id int)",
   568  			to:   "create table t(id int); create view v1 as select * from t",
   569  			diffs: []string{
   570  				"drop table v1",
   571  				"create view v1 as select * from t",
   572  			},
   573  			cdiffs: []string{
   574  				"DROP TABLE `v1`",
   575  				"CREATE VIEW `v1` AS SELECT * FROM `t`",
   576  			},
   577  		},
   578  		{
   579  			name: "convert view to table",
   580  			from: "create table t(id int); create view v1 as select * from t",
   581  			to:   "create table t(id int); create table v1 (id int)",
   582  			diffs: []string{
   583  				"drop view v1",
   584  				"create table v1 (\n\tid int\n)",
   585  			},
   586  			cdiffs: []string{
   587  				"DROP VIEW `v1`",
   588  				"CREATE TABLE `v1` (\n\t`id` int\n)",
   589  			},
   590  		},
   591  		{
   592  			name:        "unsupported statement",
   593  			from:        "create table t(id int)",
   594  			to:          "drop table t",
   595  			expectError: (&UnsupportedStatementError{Statement: "DROP TABLE `t`"}).Error(),
   596  		},
   597  		{
   598  			name: "create, alter, drop tables and views",
   599  			from: "create view v1 as select * from t1; create table t1(id int primary key); create table t2(id int primary key); create view v2 as select * from t2; create table t3(id int primary key);",
   600  			to:   "create view v0 as select * from v2, t2; create table t4(id int primary key); create view v2 as select id from t2; create table t2(id bigint primary key); create table t3(id int primary key)",
   601  			diffs: []string{
   602  				"drop table t1",
   603  				"drop view v1",
   604  				"alter table t2 modify column id bigint",
   605  				"alter view v2 as select id from t2",
   606  				"create table t4 (\n\tid int,\n\tprimary key (id)\n)",
   607  				"create view v0 as select * from v2, t2",
   608  			},
   609  			cdiffs: []string{
   610  				"DROP TABLE `t1`",
   611  				"DROP VIEW `v1`",
   612  				"ALTER TABLE `t2` MODIFY COLUMN `id` bigint",
   613  				"ALTER VIEW `v2` AS SELECT `id` FROM `t2`",
   614  				"CREATE TABLE `t4` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)",
   615  				"CREATE VIEW `v0` AS SELECT * FROM `v2`, `t2`",
   616  			},
   617  		},
   618  	}
   619  	for _, ts := range tt {
   620  		t.Run(ts.name, func(t *testing.T) {
   621  			hints := &DiffHints{
   622  				TableRenameStrategy: ts.tableRename,
   623  			}
   624  			diffs, err := DiffSchemasSQL(ts.from, ts.to, hints)
   625  			if ts.expectError != "" {
   626  				require.Error(t, err)
   627  				assert.Contains(t, err.Error(), ts.expectError)
   628  			} else {
   629  				assert.NoError(t, err)
   630  
   631  				statements := []string{}
   632  				cstatements := []string{}
   633  				for _, d := range diffs {
   634  					statements = append(statements, d.StatementString())
   635  					cstatements = append(cstatements, d.CanonicalStatementString())
   636  				}
   637  				if ts.diffs == nil {
   638  					ts.diffs = []string{}
   639  				}
   640  				assert.Equal(t, ts.diffs, statements)
   641  				if ts.cdiffs == nil {
   642  					ts.cdiffs = []string{}
   643  				}
   644  				assert.Equal(t, ts.cdiffs, cstatements)
   645  
   646  				// validate we can parse back the diff statements
   647  				for _, s := range statements {
   648  					_, err := sqlparser.ParseStrictDDL(s)
   649  					assert.NoError(t, err)
   650  				}
   651  				for _, s := range cstatements {
   652  					_, err := sqlparser.ParseStrictDDL(s)
   653  					assert.NoError(t, err)
   654  				}
   655  
   656  				{
   657  					// Validate "apply()" on "from" converges with "to"
   658  					schema1, err := NewSchemaFromSQL(ts.from)
   659  					assert.NoError(t, err)
   660  					schema1SQL := schema1.ToSQL()
   661  
   662  					schema2, err := NewSchemaFromSQL(ts.to)
   663  					assert.NoError(t, err)
   664  					applied, err := schema1.Apply(diffs)
   665  					require.NoError(t, err)
   666  
   667  					// validate schema1 unaffected by Apply
   668  					assert.Equal(t, schema1SQL, schema1.ToSQL())
   669  
   670  					appliedDiff, err := schema2.Diff(applied, hints)
   671  					require.NoError(t, err)
   672  					assert.Empty(t, appliedDiff)
   673  					assert.Equal(t, schema2.ToQueries(), applied.ToQueries())
   674  				}
   675  			}
   676  		})
   677  	}
   678  }
   679  
   680  func TestSchemaApplyError(t *testing.T) {
   681  	tt := []struct {
   682  		name string
   683  		from string
   684  		to   string
   685  	}{
   686  		{
   687  			name: "added table",
   688  			to:   "create table t2(id int primary key)",
   689  		},
   690  		{
   691  			name: "different tables",
   692  			from: "create table t1(id int primary key)",
   693  			to:   "create table t2(id int primary key)",
   694  		},
   695  		{
   696  			name: "added table 2",
   697  			from: "create table t1(id int primary key)",
   698  			to:   "create table t1(id int primary key); create table t2(id int primary key)",
   699  		},
   700  		{
   701  			name: "modified tables",
   702  			from: "create table t(id int primary key, i int)",
   703  			to:   "create table t(id int primary key)",
   704  		},
   705  		{
   706  			name: "added view",
   707  			from: "create table t(id int); create view v1 as select * from t",
   708  			to:   "create table t(id int); create view v1 as select * from t; create view v2 as select * from t",
   709  		},
   710  	}
   711  	hints := &DiffHints{}
   712  	for _, ts := range tt {
   713  		t.Run(ts.name, func(t *testing.T) {
   714  			// Validate "apply()" on "from" converges with "to"
   715  			schema1, err := NewSchemaFromSQL(ts.from)
   716  			assert.NoError(t, err)
   717  			schema2, err := NewSchemaFromSQL(ts.to)
   718  			assert.NoError(t, err)
   719  
   720  			{
   721  				diffs, err := schema1.Diff(schema2, hints)
   722  				assert.NoError(t, err)
   723  				assert.NotEmpty(t, diffs)
   724  				_, err = schema1.Apply(diffs)
   725  				require.NoError(t, err)
   726  				_, err = schema2.Apply(diffs)
   727  				require.Error(t, err, "expected error applying to schema2. diffs: %v", diffs)
   728  			}
   729  			{
   730  				diffs, err := schema2.Diff(schema1, hints)
   731  				assert.NoError(t, err)
   732  				assert.NotEmpty(t, diffs, "schema1: %v, schema2: %v", schema1.ToSQL(), schema2.ToSQL())
   733  				_, err = schema2.Apply(diffs)
   734  				require.NoError(t, err)
   735  				_, err = schema1.Apply(diffs)
   736  				require.Error(t, err, "applying diffs to schema1: %v", schema1.ToSQL())
   737  			}
   738  		})
   739  	}
   740  }