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 }