vitess.io/vitess@v0.16.2/go/vt/schemadiff/schema_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  	"strings"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"vitess.io/vitess/go/vt/sqlparser"
    27  )
    28  
    29  var createQueries = []string{
    30  	"create view v5 as select * from t1, (select * from v3) as some_alias",
    31  	"create table t3(id int, type enum('foo', 'bar') NOT NULL DEFAULT 'foo')",
    32  	"create table t1(id int)",
    33  	"create view v6 as select * from v4",
    34  	"create view v4 as select * from t2 as something_else, v3",
    35  	"create table t2(id int)",
    36  	"create table t5(id int)",
    37  	"create view v2 as select * from v3, t2",
    38  	"create view v1 as select * from v3",
    39  	"create view v3 as select * from t3 as t3",
    40  	"create view v0 as select 1 from DUAL",
    41  	"create view v9 as select 1",
    42  }
    43  
    44  var expectSortedNames = []string{
    45  	"t1",
    46  	"t2",
    47  	"t3",
    48  	"t5",
    49  	"v0", // level 1 ("dual" is an implicit table)
    50  	"v3", // level 1
    51  	"v9", // level 1 (no source table)
    52  	"v1", // level 2
    53  	"v2", // level 2
    54  	"v4", // level 2
    55  	"v5", // level 2
    56  	"v6", // level 3
    57  }
    58  
    59  var expectSortedTableNames = []string{
    60  	"t1",
    61  	"t2",
    62  	"t3",
    63  	"t5",
    64  }
    65  
    66  var expectSortedViewNames = []string{
    67  	"v0", // level 1 ("dual" is an implicit table)
    68  	"v3", // level 1
    69  	"v9", // level 1 (no source table)
    70  	"v1", // level 2
    71  	"v2", // level 2
    72  	"v4", // level 2
    73  	"v5", // level 2
    74  	"v6", // level 3
    75  }
    76  
    77  var toSQL = "CREATE TABLE `t1` (\n\t`id` int\n);\nCREATE TABLE `t2` (\n\t`id` int\n);\nCREATE TABLE `t3` (\n\t`id` int,\n\t`type` enum('foo', 'bar') NOT NULL DEFAULT 'foo'\n);\nCREATE TABLE `t5` (\n\t`id` int\n);\nCREATE VIEW `v0` AS SELECT 1 FROM `dual`;\nCREATE VIEW `v3` AS SELECT * FROM `t3` AS `t3`;\nCREATE VIEW `v9` AS SELECT 1 FROM `dual`;\nCREATE VIEW `v1` AS SELECT * FROM `v3`;\nCREATE VIEW `v2` AS SELECT * FROM `v3`, `t2`;\nCREATE VIEW `v4` AS SELECT * FROM `t2` AS `something_else`, `v3`;\nCREATE VIEW `v5` AS SELECT * FROM `t1`, (SELECT * FROM `v3`) AS `some_alias`;\nCREATE VIEW `v6` AS SELECT * FROM `v4`;\n"
    78  
    79  func TestNewSchemaFromQueries(t *testing.T) {
    80  	schema, err := NewSchemaFromQueries(createQueries)
    81  	assert.NoError(t, err)
    82  	assert.NotNil(t, schema)
    83  
    84  	assert.Equal(t, expectSortedNames, schema.EntityNames())
    85  	assert.Equal(t, expectSortedTableNames, schema.TableNames())
    86  	assert.Equal(t, expectSortedViewNames, schema.ViewNames())
    87  }
    88  
    89  func TestNewSchemaFromSQL(t *testing.T) {
    90  	schema, err := NewSchemaFromSQL(strings.Join(createQueries, ";"))
    91  	assert.NoError(t, err)
    92  	assert.NotNil(t, schema)
    93  
    94  	assert.Equal(t, expectSortedNames, schema.EntityNames())
    95  	assert.Equal(t, expectSortedTableNames, schema.TableNames())
    96  	assert.Equal(t, expectSortedViewNames, schema.ViewNames())
    97  }
    98  
    99  func TestNewSchemaFromQueriesWithDuplicate(t *testing.T) {
   100  	// v2 already exists
   101  	queries := append(createQueries,
   102  		"create view v2 as select * from v1, t2",
   103  	)
   104  	_, err := NewSchemaFromQueries(queries)
   105  	assert.Error(t, err)
   106  	assert.EqualError(t, err, (&ApplyDuplicateEntityError{Entity: "v2"}).Error())
   107  }
   108  
   109  func TestNewSchemaFromQueriesUnresolved(t *testing.T) {
   110  	// v8 does not exist
   111  	queries := append(createQueries,
   112  		"create view v7 as select * from v8, t2",
   113  	)
   114  	_, err := NewSchemaFromQueries(queries)
   115  	assert.Error(t, err)
   116  	assert.EqualError(t, err, (&ViewDependencyUnresolvedError{View: "v7"}).Error())
   117  }
   118  
   119  func TestNewSchemaFromQueriesUnresolvedAlias(t *testing.T) {
   120  	// v8 does not exist
   121  	queries := append(createQueries,
   122  		"create view v7 as select * from something_else as t1, t2",
   123  	)
   124  	_, err := NewSchemaFromQueries(queries)
   125  	assert.Error(t, err)
   126  	assert.EqualError(t, err, (&ViewDependencyUnresolvedError{View: "v7"}).Error())
   127  }
   128  
   129  func TestNewSchemaFromQueriesViewFromDual(t *testing.T) {
   130  	// Schema will not contain any tables, just a view selecting from DUAL
   131  	queries := []string{
   132  		"create view v20 as select 1 from dual",
   133  	}
   134  	_, err := NewSchemaFromQueries(queries)
   135  	assert.NoError(t, err)
   136  }
   137  
   138  func TestNewSchemaFromQueriesViewFromDualImplicit(t *testing.T) {
   139  	// Schema will not contain any tables, just a view implicitly selecting from DUAL
   140  	queries := []string{
   141  		"create view v20 as select 1",
   142  	}
   143  	_, err := NewSchemaFromQueries(queries)
   144  	assert.NoError(t, err)
   145  }
   146  
   147  func TestNewSchemaFromQueriesLoop(t *testing.T) {
   148  	// v7 and v8 depend on each other
   149  	queries := append(createQueries,
   150  		"create view v7 as select * from v8, t2",
   151  		"create view v8 as select * from t1, v7",
   152  	)
   153  	_, err := NewSchemaFromQueries(queries)
   154  	assert.Error(t, err)
   155  	assert.EqualError(t, err, (&ViewDependencyUnresolvedError{View: "v7"}).Error())
   156  }
   157  
   158  func TestToSQL(t *testing.T) {
   159  	schema, err := NewSchemaFromQueries(createQueries)
   160  	assert.NoError(t, err)
   161  	assert.NotNil(t, schema)
   162  
   163  	sql := schema.ToSQL()
   164  	assert.Equal(t, toSQL, sql)
   165  }
   166  
   167  func TestCopy(t *testing.T) {
   168  	schema, err := NewSchemaFromQueries(createQueries)
   169  	assert.NoError(t, err)
   170  	assert.NotNil(t, schema)
   171  
   172  	schemaClone := schema.copy()
   173  	assert.Equal(t, schema, schemaClone)
   174  	assert.Equal(t, schema.ToSQL(), schemaClone.ToSQL())
   175  	assert.False(t, schema == schemaClone)
   176  }
   177  
   178  func TestGetViewDependentTableNames(t *testing.T) {
   179  	tt := []struct {
   180  		name   string
   181  		view   string
   182  		tables []string
   183  	}{
   184  		{
   185  			view:   "create view v6 as select * from v4",
   186  			tables: []string{"v4"},
   187  		},
   188  		{
   189  			view:   "create view v2 as select * from v3, t2",
   190  			tables: []string{"v3", "t2"},
   191  		},
   192  		{
   193  			view:   "create view v3 as select * from t3 as t3",
   194  			tables: []string{"t3"},
   195  		},
   196  		{
   197  			view:   "create view v3 as select * from t3 as something_else",
   198  			tables: []string{"t3"},
   199  		},
   200  		{
   201  			view:   "create view v5 as select * from t1, (select * from v3) as some_alias",
   202  			tables: []string{"t1", "v3"},
   203  		},
   204  		{
   205  			view:   "create view v0 as select 1 from DUAL",
   206  			tables: []string{"dual"},
   207  		},
   208  		{
   209  			view:   "create view v9 as select 1",
   210  			tables: []string{"dual"},
   211  		},
   212  	}
   213  	for _, ts := range tt {
   214  		t.Run(ts.view, func(t *testing.T) {
   215  			stmt, err := sqlparser.ParseStrictDDL(ts.view)
   216  			require.NoError(t, err)
   217  			createView, ok := stmt.(*sqlparser.CreateView)
   218  			require.True(t, ok)
   219  
   220  			tables, err := getViewDependentTableNames(createView)
   221  			assert.NoError(t, err)
   222  			assert.Equal(t, ts.tables, tables)
   223  		})
   224  	}
   225  }
   226  
   227  func TestGetForeignKeyParentTableNames(t *testing.T) {
   228  	tt := []struct {
   229  		name   string
   230  		table  string
   231  		tables []string
   232  	}{
   233  		{
   234  			table:  "create table t1 (id int primary key, i int, foreign key (i) references parent(id))",
   235  			tables: []string{"parent"},
   236  		},
   237  		{
   238  			table:  "create table t1 (id int primary key, i int, constraint f foreign key (i) references parent(id))",
   239  			tables: []string{"parent"},
   240  		},
   241  		{
   242  			table:  "create table t1 (id int primary key, i int, constraint f foreign key (i) references parent(id) on delete cascade)",
   243  			tables: []string{"parent"},
   244  		},
   245  		{
   246  			table:  "create table t1 (id int primary key, i int, i2 int, constraint f foreign key (i) references parent(id) on delete cascade, constraint f2 foreign key (i2) references parent2(id) on delete restrict)",
   247  			tables: []string{"parent", "parent2"},
   248  		},
   249  		{
   250  			table:  "create table t1 (id int primary key, i int, i2 int, constraint f foreign key (i) references parent(id) on delete cascade, constraint f2 foreign key (i2) references parent(id) on delete restrict)",
   251  			tables: []string{"parent", "parent"},
   252  		},
   253  	}
   254  	for _, ts := range tt {
   255  		t.Run(ts.table, func(t *testing.T) {
   256  			stmt, err := sqlparser.ParseStrictDDL(ts.table)
   257  			require.NoError(t, err)
   258  			createTable, ok := stmt.(*sqlparser.CreateTable)
   259  			require.True(t, ok)
   260  
   261  			tables, err := getForeignKeyParentTableNames(createTable)
   262  			assert.NoError(t, err)
   263  			assert.Equal(t, ts.tables, tables)
   264  		})
   265  	}
   266  }
   267  
   268  func TestTableForeignKeyOrdering(t *testing.T) {
   269  	fkQueries := []string{
   270  		"create table t11 (id int primary key, i int, key ix (i), constraint f12 foreign key (i) references t12(id) on delete restrict, constraint f20 foreign key (i) references t20(id) on delete restrict)",
   271  		"create table t15(id int, primary key(id))",
   272  		"create view v09 as select * from v13, t17",
   273  		"create table t20 (id int primary key, i int, key ix (i), constraint f15 foreign key (i) references t15(id) on delete restrict)",
   274  		"create view v13 as select * from t20",
   275  		"create table t12 (id int primary key, i int, key ix (i), constraint f15 foreign key (i) references t15(id) on delete restrict)",
   276  		"create table t17 (id int primary key, i int, key ix (i), constraint f11 foreign key (i) references t11(id) on delete restrict, constraint f15 foreign key (i) references t15(id) on delete restrict)",
   277  		"create table t16 (id int primary key, i int, key ix (i), constraint f11 foreign key (i) references t11(id) on delete restrict, constraint f15 foreign key (i) references t15(id) on delete restrict)",
   278  		"create table t14 (id int primary key, i int, key ix (i), constraint f14 foreign key (i) references t14(id) on delete restrict)",
   279  	}
   280  	expectSortedTableNames := []string{
   281  		"t14",
   282  		"t15",
   283  		"t12",
   284  		"t20",
   285  		"t11",
   286  		"t16",
   287  		"t17",
   288  	}
   289  	expectSortedViewNames := []string{
   290  		"v13",
   291  		"v09",
   292  	}
   293  	schema, err := NewSchemaFromQueries(fkQueries)
   294  	require.NoError(t, err)
   295  	assert.NotNil(t, schema)
   296  
   297  	assert.Equal(t, append(expectSortedTableNames, expectSortedViewNames...), schema.EntityNames())
   298  	assert.Equal(t, expectSortedTableNames, schema.TableNames())
   299  	assert.Equal(t, expectSortedViewNames, schema.ViewNames())
   300  }
   301  
   302  func TestInvalidSchema(t *testing.T) {
   303  	tt := []struct {
   304  		schema    string
   305  		expectErr error
   306  	}{
   307  		{
   308  			schema: "create table t11 (id int primary key, i int, key ix(i), constraint f11 foreign key (i) references t11(id) on delete restrict)",
   309  		},
   310  		{
   311  			schema: "create table t10(id int primary key); create table t11 (id int primary key, i int, key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   312  		},
   313  		{
   314  			schema:    "create table t11 (id int primary key, i int, constraint f11 foreign key (i7) references t11(id) on delete restrict)",
   315  			expectErr: &InvalidColumnInForeignKeyConstraintError{Table: "t11", Constraint: "f11", Column: "i7"},
   316  		},
   317  		{
   318  			schema:    "create table t11 (id int primary key, i int, constraint f11 foreign key (i) references t11(id, i) on delete restrict)",
   319  			expectErr: &ForeignKeyColumnCountMismatchError{Table: "t11", Constraint: "f11", ColumnCount: 1, ReferencedTable: "t11", ReferencedColumnCount: 2},
   320  		},
   321  		{
   322  			schema:    "create table t11 (id int primary key, i1 int, i2 int, constraint f11 foreign key (i1, i2) references t11(i1) on delete restrict)",
   323  			expectErr: &ForeignKeyColumnCountMismatchError{Table: "t11", Constraint: "f11", ColumnCount: 2, ReferencedTable: "t11", ReferencedColumnCount: 1},
   324  		},
   325  		{
   326  			schema:    "create table t11 (id int primary key, i int, constraint f12 foreign key (i) references t12(id) on delete restrict)",
   327  			expectErr: &ForeignKeyDependencyUnresolvedError{Table: "t11"},
   328  		},
   329  		{
   330  			schema:    "create table t11 (id int primary key, i int, key ix(i), constraint f11 foreign key (i) references t11(id2) on delete restrict)",
   331  			expectErr: &InvalidReferencedColumnInForeignKeyConstraintError{Table: "t11", Constraint: "f11", ReferencedTable: "t11", ReferencedColumn: "id2"},
   332  		},
   333  		{
   334  			schema:    "create table t10(id int primary key); create table t11 (id int primary key, i int, key ix(i), constraint f10 foreign key (i) references t10(x) on delete restrict)",
   335  			expectErr: &InvalidReferencedColumnInForeignKeyConstraintError{Table: "t11", Constraint: "f10", ReferencedTable: "t10", ReferencedColumn: "x"},
   336  		},
   337  		{
   338  			schema:    "create table t10(id int primary key, i int); create table t11 (id int primary key, i int, key ix(i), constraint f10 foreign key (i) references t10(i) on delete restrict)",
   339  			expectErr: &MissingForeignKeyReferencedIndexError{Table: "t11", Constraint: "f10", ReferencedTable: "t10"},
   340  		},
   341  		{
   342  			schema:    "create table t10(id int primary key); create table t11 (id int primary key, i int unsigned, key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   343  			expectErr: &ForeignKeyColumnTypeMismatchError{Table: "t11", Constraint: "f10", Column: "i", ReferencedTable: "t10", ReferencedColumn: "id"},
   344  		},
   345  		{
   346  			schema:    "create table t10(id int primary key); create table t11 (id int primary key, i bigint, key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   347  			expectErr: &ForeignKeyColumnTypeMismatchError{Table: "t11", Constraint: "f10", Column: "i", ReferencedTable: "t10", ReferencedColumn: "id"},
   348  		},
   349  		{
   350  			schema:    "create table t10(id bigint primary key); create table t11 (id int primary key, i int, key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   351  			expectErr: &ForeignKeyColumnTypeMismatchError{Table: "t11", Constraint: "f10", Column: "i", ReferencedTable: "t10", ReferencedColumn: "id"},
   352  		},
   353  		{
   354  			schema:    "create table t10(id bigint primary key); create table t11 (id int primary key, i varchar(100), key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   355  			expectErr: &ForeignKeyColumnTypeMismatchError{Table: "t11", Constraint: "f10", Column: "i", ReferencedTable: "t10", ReferencedColumn: "id"},
   356  		},
   357  		{
   358  			// InnoDB allows different string length
   359  			schema: "create table t10(id varchar(50) primary key); create table t11 (id int primary key, i varchar(100), key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   360  		},
   361  		{
   362  			schema:    "create table t10(id varchar(50) charset utf8mb3 primary key); create table t11 (id int primary key, i varchar(100) charset utf8mb4, key ix(i), constraint f10 foreign key (i) references t10(id) on delete restrict)",
   363  			expectErr: &ForeignKeyColumnTypeMismatchError{Table: "t11", Constraint: "f10", Column: "i", ReferencedTable: "t10", ReferencedColumn: "id"},
   364  		},
   365  	}
   366  	for _, ts := range tt {
   367  		t.Run(ts.schema, func(t *testing.T) {
   368  
   369  			_, err := NewSchemaFromSQL(ts.schema)
   370  			if ts.expectErr == nil {
   371  				assert.NoError(t, err)
   372  			} else {
   373  				assert.Error(t, err)
   374  				assert.EqualError(t, err, ts.expectErr.Error())
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  func TestInvalidTableForeignKeyReference(t *testing.T) {
   381  	{
   382  		fkQueries := []string{
   383  			"create table t11 (id int primary key, i int, constraint f12 foreign key (i) references t12(id) on delete restrict)",
   384  			"create table t15(id int, primary key(id))",
   385  		}
   386  		_, err := NewSchemaFromQueries(fkQueries)
   387  		assert.Error(t, err)
   388  		assert.EqualError(t, err, (&ForeignKeyDependencyUnresolvedError{Table: "t11"}).Error())
   389  	}
   390  	{
   391  		fkQueries := []string{
   392  			"create table t13 (id int primary key, i int, constraint f11 foreign key (i) references t11(id) on delete restrict)",
   393  			"create table t11 (id int primary key, i int, constraint f12 foreign key (i) references t12(id) on delete restrict)",
   394  			"create table t12 (id int primary key, i int, constraint f13 foreign key (i) references t13(id) on delete restrict)",
   395  		}
   396  		_, err := NewSchemaFromQueries(fkQueries)
   397  		assert.Error(t, err)
   398  		assert.EqualError(t, err, (&ForeignKeyDependencyUnresolvedError{Table: "t11"}).Error())
   399  	}
   400  }