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 }