vitess.io/vitess@v0.16.2/go/vt/schemadiff/table.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 "fmt" 21 "math" 22 "sort" 23 "strconv" 24 "strings" 25 26 golcs "github.com/yudai/golcs" 27 28 "vitess.io/vitess/go/mysql/collations" 29 "vitess.io/vitess/go/vt/sqlparser" 30 ) 31 32 type AlterTableEntityDiff struct { 33 from *CreateTableEntity 34 to *CreateTableEntity 35 alterTable *sqlparser.AlterTable 36 37 subsequentDiff *AlterTableEntityDiff 38 } 39 40 // IsEmpty implements EntityDiff 41 func (d *AlterTableEntityDiff) IsEmpty() bool { 42 return d.Statement() == nil 43 } 44 45 // Entities implements EntityDiff 46 func (d *AlterTableEntityDiff) Entities() (from Entity, to Entity) { 47 return d.from, d.to 48 } 49 50 // Statement implements EntityDiff 51 func (d *AlterTableEntityDiff) Statement() sqlparser.Statement { 52 if d == nil { 53 return nil 54 } 55 return d.alterTable 56 } 57 58 // AlterTable returns the underlying sqlparser.AlterTable that was generated for the diff. 59 func (d *AlterTableEntityDiff) AlterTable() *sqlparser.AlterTable { 60 if d == nil { 61 return nil 62 } 63 return d.alterTable 64 } 65 66 // StatementString implements EntityDiff 67 func (d *AlterTableEntityDiff) StatementString() (s string) { 68 if stmt := d.Statement(); stmt != nil { 69 s = sqlparser.String(stmt) 70 } 71 return s 72 } 73 74 // CanonicalStatementString implements EntityDiff 75 func (d *AlterTableEntityDiff) CanonicalStatementString() (s string) { 76 if stmt := d.Statement(); stmt != nil { 77 s = sqlparser.CanonicalString(stmt) 78 } 79 return s 80 } 81 82 // SubsequentDiff implements EntityDiff 83 func (d *AlterTableEntityDiff) SubsequentDiff() EntityDiff { 84 if d == nil { 85 return nil 86 } 87 return d.subsequentDiff 88 } 89 90 // SetSubsequentDiff implements EntityDiff 91 func (d *AlterTableEntityDiff) SetSubsequentDiff(subDiff EntityDiff) { 92 if d == nil { 93 return 94 } 95 if subTableDiff, ok := subDiff.(*AlterTableEntityDiff); ok { 96 d.subsequentDiff = subTableDiff 97 } else { 98 d.subsequentDiff = nil 99 } 100 } 101 102 // addSubsequentDiff adds a subsequent diff to the tail of the diff sequence 103 func (d *AlterTableEntityDiff) addSubsequentDiff(diff *AlterTableEntityDiff) { 104 if d.subsequentDiff == nil { 105 d.subsequentDiff = diff 106 } else { 107 d.subsequentDiff.addSubsequentDiff(diff) 108 } 109 } 110 111 type CreateTableEntityDiff struct { 112 to *CreateTableEntity 113 createTable *sqlparser.CreateTable 114 } 115 116 // IsEmpty implements EntityDiff 117 func (d *CreateTableEntityDiff) IsEmpty() bool { 118 return d.Statement() == nil 119 } 120 121 // Entities implements EntityDiff 122 func (d *CreateTableEntityDiff) Entities() (from Entity, to Entity) { 123 return nil, &CreateTableEntity{CreateTable: d.createTable} 124 } 125 126 // Statement implements EntityDiff 127 func (d *CreateTableEntityDiff) Statement() sqlparser.Statement { 128 if d == nil { 129 return nil 130 } 131 return d.createTable 132 } 133 134 // CreateTable returns the underlying sqlparser.CreateTable that was generated for the diff. 135 func (d *CreateTableEntityDiff) CreateTable() *sqlparser.CreateTable { 136 if d == nil { 137 return nil 138 } 139 return d.createTable 140 } 141 142 // StatementString implements EntityDiff 143 func (d *CreateTableEntityDiff) StatementString() (s string) { 144 if stmt := d.Statement(); stmt != nil { 145 s = sqlparser.String(stmt) 146 } 147 return s 148 } 149 150 // CanonicalStatementString implements EntityDiff 151 func (d *CreateTableEntityDiff) CanonicalStatementString() (s string) { 152 if stmt := d.Statement(); stmt != nil { 153 s = sqlparser.CanonicalString(stmt) 154 } 155 return s 156 } 157 158 // SubsequentDiff implements EntityDiff 159 func (d *CreateTableEntityDiff) SubsequentDiff() EntityDiff { 160 return nil 161 } 162 163 // SetSubsequentDiff implements EntityDiff 164 func (d *CreateTableEntityDiff) SetSubsequentDiff(EntityDiff) { 165 } 166 167 type DropTableEntityDiff struct { 168 from *CreateTableEntity 169 dropTable *sqlparser.DropTable 170 } 171 172 // IsEmpty implements EntityDiff 173 func (d *DropTableEntityDiff) IsEmpty() bool { 174 return d.Statement() == nil 175 } 176 177 // Entities implements EntityDiff 178 func (d *DropTableEntityDiff) Entities() (from Entity, to Entity) { 179 return d.from, nil 180 } 181 182 // Statement implements EntityDiff 183 func (d *DropTableEntityDiff) Statement() sqlparser.Statement { 184 if d == nil { 185 return nil 186 } 187 return d.dropTable 188 } 189 190 // DropTable returns the underlying sqlparser.DropTable that was generated for the diff. 191 func (d *DropTableEntityDiff) DropTable() *sqlparser.DropTable { 192 if d == nil { 193 return nil 194 } 195 return d.dropTable 196 } 197 198 // StatementString implements EntityDiff 199 func (d *DropTableEntityDiff) StatementString() (s string) { 200 if stmt := d.Statement(); stmt != nil { 201 s = sqlparser.String(stmt) 202 } 203 return s 204 } 205 206 // CanonicalStatementString implements EntityDiff 207 func (d *DropTableEntityDiff) CanonicalStatementString() (s string) { 208 if stmt := d.Statement(); stmt != nil { 209 s = sqlparser.CanonicalString(stmt) 210 } 211 return s 212 } 213 214 // SubsequentDiff implements EntityDiff 215 func (d *DropTableEntityDiff) SubsequentDiff() EntityDiff { 216 return nil 217 } 218 219 // SetSubsequentDiff implements EntityDiff 220 func (d *DropTableEntityDiff) SetSubsequentDiff(EntityDiff) { 221 } 222 223 type RenameTableEntityDiff struct { 224 from *CreateTableEntity 225 to *CreateTableEntity 226 renameTable *sqlparser.RenameTable 227 } 228 229 // IsEmpty implements EntityDiff 230 func (d *RenameTableEntityDiff) IsEmpty() bool { 231 return d.Statement() == nil 232 } 233 234 // Entities implements EntityDiff 235 func (d *RenameTableEntityDiff) Entities() (from Entity, to Entity) { 236 return d.from, d.to 237 } 238 239 // Statement implements EntityDiff 240 func (d *RenameTableEntityDiff) Statement() sqlparser.Statement { 241 if d == nil { 242 return nil 243 } 244 return d.renameTable 245 } 246 247 // RenameTable returns the underlying sqlparser.RenameTable that was generated for the diff. 248 func (d *RenameTableEntityDiff) RenameTable() *sqlparser.RenameTable { 249 if d == nil { 250 return nil 251 } 252 return d.renameTable 253 } 254 255 // StatementString implements EntityDiff 256 func (d *RenameTableEntityDiff) StatementString() (s string) { 257 if stmt := d.Statement(); stmt != nil { 258 s = sqlparser.String(stmt) 259 } 260 return s 261 } 262 263 // CanonicalStatementString implements EntityDiff 264 func (d *RenameTableEntityDiff) CanonicalStatementString() (s string) { 265 if stmt := d.Statement(); stmt != nil { 266 s = sqlparser.CanonicalString(stmt) 267 } 268 return s 269 } 270 271 // SubsequentDiff implements EntityDiff 272 func (d *RenameTableEntityDiff) SubsequentDiff() EntityDiff { 273 return nil 274 } 275 276 // SetSubsequentDiff implements EntityDiff 277 func (d *RenameTableEntityDiff) SetSubsequentDiff(EntityDiff) { 278 } 279 280 // CreateTableEntity stands for a TABLE construct. It contains the table's CREATE statement. 281 type CreateTableEntity struct { 282 *sqlparser.CreateTable 283 } 284 285 func NewCreateTableEntity(c *sqlparser.CreateTable) (*CreateTableEntity, error) { 286 if !c.IsFullyParsed() { 287 return nil, &NotFullyParsedError{Entity: c.Table.Name.String(), Statement: sqlparser.CanonicalString(c)} 288 } 289 entity := &CreateTableEntity{CreateTable: c} 290 entity.normalize() 291 return entity, nil 292 } 293 294 // normalize cleans up the table definition: 295 // - setting names to all keys 296 // - table option case (upper/lower/special) 297 // The function returns this receiver as courtesy 298 func (c *CreateTableEntity) normalize() *CreateTableEntity { 299 c.normalizePrimaryKeyColumns() 300 c.normalizeForeignKeyIndexes() // implicitly add missing indexes for foreign keys 301 c.normalizeKeys() // assign names to keys 302 c.normalizeUnnamedConstraints() 303 c.normalizeTableOptions() 304 c.normalizeColumnOptions() 305 c.normalizeIndexOptions() 306 c.normalizePartitionOptions() 307 return c 308 } 309 310 func (c *CreateTableEntity) normalizeTableOptions() { 311 for _, opt := range c.CreateTable.TableSpec.Options { 312 opt.Name = strings.ToLower(opt.Name) 313 switch opt.Name { 314 case "charset": 315 opt.String = strings.ToLower(opt.String) 316 if charset, ok := collationEnv.CharsetAlias(opt.String); ok { 317 opt.String = charset 318 } 319 case "collate": 320 opt.String = strings.ToLower(opt.String) 321 if collation, ok := collationEnv.CollationAlias(opt.String); ok { 322 opt.String = collation 323 } 324 case "engine": 325 opt.String = strings.ToUpper(opt.String) 326 if engineName, ok := engineCasing[opt.String]; ok { 327 opt.String = engineName 328 } 329 case "row_format": 330 opt.String = strings.ToUpper(opt.String) 331 } 332 } 333 } 334 335 func (c *CreateTableEntity) Clone() Entity { 336 return &CreateTableEntity{CreateTable: sqlparser.CloneRefOfCreateTable(c.CreateTable)} 337 } 338 339 // Right now we assume MySQL 8.0 for the collation normalization handling. 340 const mysqlCollationVersion = "8.0.0" 341 342 var collationEnv = collations.NewEnvironment(mysqlCollationVersion) 343 344 func defaultCharset() string { 345 collation := collationEnv.LookupByID(collations.ID(collationEnv.DefaultConnectionCharset())) 346 if collation == nil { 347 return "" 348 } 349 return collation.Charset().Name() 350 } 351 352 func defaultCharsetCollation(charset string) string { 353 collation := collationEnv.DefaultCollationForCharset(charset) 354 if collation == nil { 355 return "" 356 } 357 return collation.Name() 358 } 359 360 func (c *CreateTableEntity) normalizeColumnOptions() { 361 tableCharset := defaultCharset() 362 tableCollation := "" 363 for _, option := range c.CreateTable.TableSpec.Options { 364 switch strings.ToUpper(option.Name) { 365 case "CHARSET": 366 tableCharset = option.String 367 case "COLLATE": 368 tableCollation = option.String 369 } 370 } 371 defaultCollation := defaultCharsetCollation(tableCharset) 372 if tableCollation == "" { 373 tableCollation = defaultCollation 374 } 375 376 for _, col := range c.CreateTable.TableSpec.Columns { 377 if col.Type.Options == nil { 378 col.Type.Options = &sqlparser.ColumnTypeOptions{} 379 } 380 381 // Map known lowercase fields to always be lowercase 382 col.Type.Type = strings.ToLower(col.Type.Type) 383 col.Type.Charset.Name = strings.ToLower(col.Type.Charset.Name) 384 col.Type.Options.Collate = strings.ToLower(col.Type.Options.Collate) 385 386 // See https://dev.mysql.com/doc/refman/8.0/en/create-table.html 387 // If neither NULL nor NOT NULL is specified, the column is treated as though NULL had been specified. 388 // That documentation though is not 100% true. There's an exception, and that is 389 // the `explicit_defaults_for_timestamp` flag. When that is disabled (the default on 5.7), 390 // a timestamp defaults to `NOT NULL`. 391 // 392 // We opt here to instead remove that difference and always then add `NULL` and treat 393 // `explicit_defaults_for_timestamp` as always enabled in the context of DDL for diffing. 394 if col.Type.Type == "timestamp" { 395 if col.Type.Options.Null == nil || *col.Type.Options.Null { 396 timestampNull := true 397 col.Type.Options.Null = ×tampNull 398 } 399 } else { 400 if col.Type.Options.Null != nil && *col.Type.Options.Null { 401 col.Type.Options.Null = nil 402 } 403 } 404 if col.Type.Options.Null == nil || *col.Type.Options.Null { 405 // If `DEFAULT NULL` is specified and the column allows NULL, 406 // we drop that in the normalized form since that is equivalent to the default value. 407 // See also https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html 408 if _, ok := col.Type.Options.Default.(*sqlparser.NullVal); ok { 409 col.Type.Options.Default = nil 410 } 411 } 412 413 if col.Type.Options.Invisible != nil && !*col.Type.Options.Invisible { 414 // If a column is marked `VISIBLE`, that's the same as the default. 415 col.Type.Options.Invisible = nil 416 } 417 418 // Map any charset aliases to the real charset. This applies mainly right 419 // now to utf8 being an alias for utf8mb3. 420 if charset, ok := collationEnv.CharsetAlias(col.Type.Charset.Name); ok { 421 col.Type.Charset.Name = charset 422 } 423 424 // Map any collation aliases to the real collation. This applies mainly right 425 // now to utf8 being an alias for utf8mb3 collations. 426 if collation, ok := collationEnv.CollationAlias(col.Type.Options.Collate); ok { 427 col.Type.Options.Collate = collation 428 } 429 430 // Remove any lengths for integral types since it is deprecated there and 431 // doesn't mean anything anymore. 432 if _, ok := integralTypes[col.Type.Type]; ok { 433 // We can remove the length except when we have a boolean, which is 434 // stored as a tinyint(1) and treated special. 435 if !isBool(col.Type) { 436 col.Type.Length = nil 437 } 438 } 439 440 if _, ok := floatTypes[col.Type.Type]; ok { 441 // First, normalize the actual type 442 switch col.Type.Type { 443 case "float4": 444 col.Type.Type = "float" 445 case "float8", "real": 446 col.Type.Type = "double" 447 } 448 449 if col.Type.Length != nil && col.Type.Scale == nil && col.Type.Length.Type == sqlparser.IntVal { 450 if l, err := strconv.ParseInt(col.Type.Length.Val, 10, 64); err == nil { 451 // See https://dev.mysql.com/doc/refman/8.0/en/floating-point-types.html, but the docs are 452 // subtly wrong. We use a float for a precision of 24, not a double as the documentation 453 // mentioned. Validated against the actual behavior of MySQL. 454 if l <= 24 { 455 col.Type.Type = "float" 456 } else { 457 col.Type.Type = "double" 458 } 459 } 460 col.Type.Length = nil 461 } 462 } 463 464 if _, ok := charsetTypes[col.Type.Type]; ok { 465 // If the charset is explicitly configured and it mismatches, we don't normalize 466 // anything for charsets or collations and move on. 467 if col.Type.Charset.Name != "" && col.Type.Charset.Name != tableCharset { 468 continue 469 } 470 471 // Alright, first check if both charset and collation are the same as 472 // the table level options, in that case we can remove both since that's equivalent. 473 if col.Type.Charset.Name == tableCharset && col.Type.Options.Collate == tableCollation { 474 col.Type.Charset.Name = "" 475 col.Type.Options.Collate = "" 476 } 477 // If we have no charset or collation defined, we inherit the table defaults 478 // and don't need to do anything here and can continue to the next column. 479 // It doesn't matter if that's because it's not defined, or if it was because 480 // it was explicitly set to the same values. 481 if col.Type.Charset.Name == "" && col.Type.Options.Collate == "" { 482 continue 483 } 484 485 // We have a matching charset as the default, but it is explicitly set. In that 486 // case we still want to clear it, but set the default collation for the given charset 487 // if no collation is defined yet. We set then the collation to the default collation. 488 if col.Type.Charset.Name != "" { 489 col.Type.Charset.Name = "" 490 if col.Type.Options.Collate == "" { 491 col.Type.Options.Collate = defaultCollation 492 } 493 } 494 495 // We now have one case left, which is when we have set a collation but it's the same 496 // as the table level. In that case, we can clear it since that is equivalent. 497 if col.Type.Options.Collate == tableCollation { 498 col.Type.Options.Collate = "" 499 } 500 } 501 } 502 } 503 504 func (c *CreateTableEntity) normalizeIndexOptions() { 505 for _, idx := range c.CreateTable.TableSpec.Indexes { 506 // This name is taking straight from the input string 507 // so we want to normalize this to always lowercase. 508 idx.Info.Type = strings.ToLower(idx.Info.Type) 509 for _, opt := range idx.Options { 510 opt.Name = strings.ToLower(opt.Name) 511 opt.String = strings.ToLower(opt.String) 512 } 513 } 514 } 515 516 func isBool(colType *sqlparser.ColumnType) bool { 517 return colType.Type == sqlparser.KeywordString(sqlparser.TINYINT) && colType.Length != nil && sqlparser.CanonicalString(colType.Length) == "1" 518 } 519 520 func (c *CreateTableEntity) normalizePartitionOptions() { 521 if c.CreateTable.TableSpec.PartitionOption == nil { 522 return 523 } 524 525 for _, def := range c.CreateTable.TableSpec.PartitionOption.Definitions { 526 if def.Options == nil || def.Options.Engine == nil { 527 continue 528 } 529 530 def.Options.Engine.Name = strings.ToUpper(def.Options.Engine.Name) 531 if engineName, ok := engineCasing[def.Options.Engine.Name]; ok { 532 def.Options.Engine.Name = engineName 533 } 534 } 535 } 536 537 func newPrimaryKeyIndexDefinitionSingleColumn(name sqlparser.IdentifierCI) *sqlparser.IndexDefinition { 538 index := &sqlparser.IndexDefinition{ 539 Info: &sqlparser.IndexInfo{ 540 Name: sqlparser.NewIdentifierCI("PRIMARY"), 541 Type: "PRIMARY KEY", 542 Primary: true, 543 Unique: true, 544 }, 545 Columns: []*sqlparser.IndexColumn{{Column: name}}, 546 } 547 return index 548 } 549 550 func (c *CreateTableEntity) normalizePrimaryKeyColumns() { 551 // normalize PRIMARY KEY: 552 // `create table t (id int primary key)` 553 // should turn into: 554 // `create table t (id int, primary key (id))` 555 // Also, PRIMARY KEY must come first before all other keys 556 for _, col := range c.CreateTable.TableSpec.Columns { 557 if col.Type.Options.KeyOpt == sqlparser.ColKeyPrimary { 558 c.CreateTable.TableSpec.Indexes = append([]*sqlparser.IndexDefinition{newPrimaryKeyIndexDefinitionSingleColumn(col.Name)}, c.CreateTable.TableSpec.Indexes...) 559 col.Type.Options.KeyOpt = sqlparser.ColKeyNone 560 } 561 } 562 } 563 564 func (c *CreateTableEntity) normalizeKeys() { 565 c.normalizePrimaryKeyColumns() 566 567 // let's ensure all keys have names 568 keyNameExists := map[string]bool{} 569 // first, we iterate and take note for all keys that do already have names 570 for _, key := range c.CreateTable.TableSpec.Indexes { 571 if name := key.Info.Name.Lowered(); name != "" { 572 keyNameExists[name] = true 573 } 574 } 575 for _, key := range c.CreateTable.TableSpec.Indexes { 576 // Normalize to KEY which matches MySQL behavior for the type. 577 if key.Info.Type == sqlparser.KeywordString(sqlparser.INDEX) { 578 key.Info.Type = sqlparser.KeywordString(sqlparser.KEY) 579 } 580 // now, let's look at keys that do not have names, and assign them new names 581 if name := key.Info.Name.String(); name == "" { 582 // we know there must be at least one column covered by this key 583 var colName string 584 if len(key.Columns) > 0 { 585 expressionFound := false 586 for _, col := range key.Columns { 587 if col.Expression != nil { 588 expressionFound = true 589 } 590 } 591 if expressionFound { 592 // that's the name MySQL picks for an unnamed key when there's at least one functional index expression 593 colName = "functional_index" 594 } else { 595 // like MySQL, we first try to call our index by the name of the first column: 596 colName = key.Columns[0].Column.String() 597 } 598 } 599 suggestedKeyName := colName 600 // now let's see if that name is taken; if it is, enumerate new news until we find a free name 601 for enumerate := 2; keyNameExists[strings.ToLower(suggestedKeyName)]; enumerate++ { 602 suggestedKeyName = fmt.Sprintf("%s_%d", colName, enumerate) 603 } 604 // OK we found a free slot! 605 key.Info.Name = sqlparser.NewIdentifierCI(suggestedKeyName) 606 keyNameExists[strings.ToLower(suggestedKeyName)] = true 607 } 608 609 // Drop options that are the same as the default. 610 keptOptions := make([]*sqlparser.IndexOption, 0, len(key.Options)) 611 for _, option := range key.Options { 612 switch strings.ToUpper(option.Name) { 613 case "USING": 614 if strings.EqualFold(option.String, "BTREE") { 615 continue 616 } 617 case "VISIBLE": 618 continue 619 } 620 keptOptions = append(keptOptions, option) 621 } 622 key.Options = keptOptions 623 } 624 } 625 626 func (c *CreateTableEntity) normalizeUnnamedConstraints() { 627 // let's ensure all constraints have names 628 constraintNameExists := map[string]bool{} 629 // first, we iterate and take note for all keys that do already have names 630 for _, constraint := range c.CreateTable.TableSpec.Constraints { 631 if name := constraint.Name.Lowered(); name != "" { 632 constraintNameExists[name] = true 633 } 634 } 635 636 // now, let's look at keys that do not have names, and assign them new names 637 for _, constraint := range c.CreateTable.TableSpec.Constraints { 638 if name := constraint.Name.String(); name == "" { 639 nameFormat := "%s_chk_%d" 640 if _, fk := constraint.Details.(*sqlparser.ForeignKeyDefinition); fk { 641 nameFormat = "%s_ibfk_%d" 642 } 643 suggestedCheckName := fmt.Sprintf(nameFormat, c.CreateTable.Table.Name.String(), 1) 644 // now let's see if that name is taken; if it is, enumerate new news until we find a free name 645 for enumerate := 2; constraintNameExists[strings.ToLower(suggestedCheckName)]; enumerate++ { 646 suggestedCheckName = fmt.Sprintf(nameFormat, c.CreateTable.Table.Name.String(), enumerate) 647 } 648 // OK we found a free slot! 649 constraint.Name = sqlparser.NewIdentifierCI(suggestedCheckName) 650 constraintNameExists[strings.ToLower(suggestedCheckName)] = true 651 } 652 } 653 } 654 655 func (c *CreateTableEntity) normalizeForeignKeyIndexes() { 656 for _, constraint := range c.CreateTable.TableSpec.Constraints { 657 fk, ok := constraint.Details.(*sqlparser.ForeignKeyDefinition) 658 if !ok { 659 continue 660 } 661 if !c.columnsCoveredByInOrderIndex(fk.Source) { 662 // We add a foreign key, but the local FK columns are not indexed. 663 // MySQL's behavior is to implicitly add an index that covers the foreign key's local columns. 664 // The name of the index is either: 665 // - the same name of the constraint, if such name is provided 666 // - and error if an index by this name exists 667 // - or, a standard auto-generated index name, if the constraint name is not provided 668 indexDefinition := &sqlparser.IndexDefinition{ 669 Info: &sqlparser.IndexInfo{ 670 Type: "key", 671 Name: constraint.Name, // if name is empty, then the name is later auto populated 672 }, 673 } 674 for _, col := range fk.Source { 675 indexColumn := &sqlparser.IndexColumn{Column: col} 676 indexDefinition.Columns = append(indexDefinition.Columns, indexColumn) 677 } 678 c.TableSpec.Indexes = append(c.TableSpec.Indexes, indexDefinition) 679 } 680 } 681 } 682 683 // Name implements Entity interface 684 func (c *CreateTableEntity) Name() string { 685 return c.CreateTable.GetTable().Name.String() 686 } 687 688 // Diff implements Entity interface function 689 func (c *CreateTableEntity) Diff(other Entity, hints *DiffHints) (EntityDiff, error) { 690 otherCreateTable, ok := other.(*CreateTableEntity) 691 if !ok { 692 return nil, ErrEntityTypeMismatch 693 } 694 if hints.StrictIndexOrdering { 695 return nil, ErrStrictIndexOrderingUnsupported 696 } 697 if c.CreateTable.TableSpec == nil { 698 return nil, ErrUnexpectedTableSpec 699 } 700 701 d, err := c.TableDiff(otherCreateTable, hints) 702 if err != nil { 703 return nil, err 704 } 705 return d, nil 706 } 707 708 // TableDiff compares this table statement with another table statement, and sees what it takes to 709 // change this table to look like the other table. 710 // It returns an AlterTable statement if changes are found, or nil if not. 711 // the other table may be of different name; its name is ignored. 712 func (c *CreateTableEntity) TableDiff(other *CreateTableEntity, hints *DiffHints) (*AlterTableEntityDiff, error) { 713 if !c.CreateTable.IsFullyParsed() { 714 return nil, &NotFullyParsedError{Entity: c.Name(), Statement: sqlparser.CanonicalString(c.CreateTable)} 715 } 716 if !other.CreateTable.IsFullyParsed() { 717 return nil, &NotFullyParsedError{Entity: other.Name(), Statement: sqlparser.CanonicalString(other.CreateTable)} 718 } 719 720 if c.identicalOtherThanName(other) { 721 return nil, nil 722 } 723 724 alterTable := &sqlparser.AlterTable{ 725 Table: c.CreateTable.Table, 726 } 727 diffedTableCharset := "" 728 var parentAlterTableEntityDiff *AlterTableEntityDiff 729 var partitionSpecs []*sqlparser.PartitionSpec 730 var superfluousFulltextKeys []*sqlparser.AddIndexDefinition 731 { 732 t1Options := c.CreateTable.TableSpec.Options 733 t2Options := other.CreateTable.TableSpec.Options 734 diffedTableCharset = c.diffTableCharset(t1Options, t2Options) 735 } 736 { 737 // diff columns 738 // ordered columns for both tables: 739 t1Columns := c.CreateTable.TableSpec.Columns 740 t2Columns := other.CreateTable.TableSpec.Columns 741 c.diffColumns(alterTable, t1Columns, t2Columns, hints, diffedTableCharset != "") 742 } 743 { 744 // diff keys 745 // ordered keys for both tables: 746 t1Keys := c.CreateTable.TableSpec.Indexes 747 t2Keys := other.CreateTable.TableSpec.Indexes 748 superfluousFulltextKeys = c.diffKeys(alterTable, t1Keys, t2Keys, hints) 749 } 750 { 751 // diff constraints 752 // ordered constraints for both tables: 753 t1Constraints := c.CreateTable.TableSpec.Constraints 754 t2Constraints := other.CreateTable.TableSpec.Constraints 755 c.diffConstraints(alterTable, t1Constraints, t2Constraints, hints) 756 } 757 { 758 // diff partitions 759 // ordered keys for both tables: 760 t1Partitions := c.CreateTable.TableSpec.PartitionOption 761 t2Partitions := other.CreateTable.TableSpec.PartitionOption 762 var err error 763 partitionSpecs, err = c.diffPartitions(alterTable, t1Partitions, t2Partitions, hints) 764 if err != nil { 765 return nil, err 766 } 767 } 768 { 769 // diff table options 770 // ordered keys for both tables: 771 t1Options := c.CreateTable.TableSpec.Options 772 t2Options := other.CreateTable.TableSpec.Options 773 if err := c.diffOptions(alterTable, t1Options, t2Options, hints); err != nil { 774 return nil, err 775 } 776 } 777 tableSpecHasChanged := len(alterTable.AlterOptions) > 0 || alterTable.PartitionOption != nil || alterTable.PartitionSpec != nil 778 779 newAlterTableEntityDiff := func(alterTable *sqlparser.AlterTable) *AlterTableEntityDiff { 780 d := &AlterTableEntityDiff{alterTable: alterTable, from: c, to: other} 781 782 var algorithmValue sqlparser.AlgorithmValue 783 784 switch hints.AlterTableAlgorithmStrategy { 785 case AlterTableAlgorithmStrategyCopy: 786 algorithmValue = sqlparser.AlgorithmValue("COPY") 787 case AlterTableAlgorithmStrategyInplace: 788 algorithmValue = sqlparser.AlgorithmValue("INPLACE") 789 case AlterTableAlgorithmStrategyInstant: 790 algorithmValue = sqlparser.AlgorithmValue("INSTANT") 791 } 792 if algorithmValue != "" { 793 alterTable.AlterOptions = append(alterTable.AlterOptions, algorithmValue) 794 } 795 return d 796 } 797 if tableSpecHasChanged { 798 parentAlterTableEntityDiff = newAlterTableEntityDiff(alterTable) 799 } 800 for _, superfluousFulltextKey := range superfluousFulltextKeys { 801 alterTable := &sqlparser.AlterTable{ 802 Table: c.CreateTable.Table, 803 AlterOptions: []sqlparser.AlterOption{superfluousFulltextKey}, 804 } 805 diff := newAlterTableEntityDiff(alterTable) 806 // if we got superfluous fulltext keys, that means the table spec has changed, ie 807 // parentAlterTableEntityDiff is not nil 808 parentAlterTableEntityDiff.addSubsequentDiff(diff) 809 } 810 for _, partitionSpec := range partitionSpecs { 811 alterTable := &sqlparser.AlterTable{ 812 Table: c.CreateTable.Table, 813 PartitionSpec: partitionSpec, 814 } 815 diff := newAlterTableEntityDiff(alterTable) 816 if parentAlterTableEntityDiff == nil { 817 parentAlterTableEntityDiff = diff 818 } else { 819 parentAlterTableEntityDiff.addSubsequentDiff(diff) 820 } 821 } 822 return parentAlterTableEntityDiff, nil 823 } 824 825 func (c *CreateTableEntity) diffTableCharset( 826 t1Options sqlparser.TableOptions, 827 t2Options sqlparser.TableOptions, 828 ) string { 829 getcharset := func(options sqlparser.TableOptions) string { 830 for _, option := range options { 831 if strings.EqualFold(option.Name, "CHARSET") { 832 return option.String 833 } 834 } 835 return "" 836 } 837 t1Charset := getcharset(t1Options) 838 t2Charset := getcharset(t2Options) 839 if t1Charset != t2Charset { 840 return t2Charset 841 } 842 return "" 843 } 844 845 // isDefaultTableOptionValue sees if the value for a TableOption is also its default value 846 func isDefaultTableOptionValue(option *sqlparser.TableOption) bool { 847 var value string 848 if option.Value != nil { 849 value = sqlparser.CanonicalString(option.Value) 850 } 851 switch strings.ToUpper(option.Name) { 852 case "CHECKSUM": 853 return value == "0" 854 case "COMMENT": 855 return option.String == "" 856 case "COMPRESSION": 857 return value == "" || value == "''" 858 case "CONNECTION": 859 return value == "" || value == "''" 860 case "DATA DIRECTORY": 861 return value == "" || value == "''" 862 case "DELAY_KEY_WRITE": 863 return value == "0" 864 case "ENCRYPTION": 865 return value == "N" 866 case "INDEX DIRECTORY": 867 return value == "" || value == "''" 868 case "KEY_BLOCK_SIZE": 869 return value == "0" 870 case "MAX_ROWS": 871 return value == "0" 872 case "MIN_ROWS": 873 return value == "0" 874 case "PACK_KEYS": 875 return strings.EqualFold(option.String, "DEFAULT") 876 case "ROW_FORMAT": 877 return strings.EqualFold(option.String, "DEFAULT") 878 case "STATS_AUTO_RECALC": 879 return strings.EqualFold(option.String, "DEFAULT") 880 case "STATS_PERSISTENT": 881 return strings.EqualFold(option.String, "DEFAULT") 882 case "STATS_SAMPLE_PAGES": 883 return strings.EqualFold(option.String, "DEFAULT") 884 default: 885 return false 886 } 887 } 888 889 func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, 890 t1Options sqlparser.TableOptions, 891 t2Options sqlparser.TableOptions, 892 hints *DiffHints, 893 ) error { 894 t1OptionsMap := map[string]*sqlparser.TableOption{} 895 t2OptionsMap := map[string]*sqlparser.TableOption{} 896 for _, option := range t1Options { 897 t1OptionsMap[option.Name] = option 898 } 899 for _, option := range t2Options { 900 t2OptionsMap[option.Name] = option 901 } 902 alterTableOptions := sqlparser.TableOptions{} 903 // dropped options 904 for _, t1Option := range t1Options { 905 if _, ok := t2OptionsMap[t1Option.Name]; !ok { 906 // option exists in t1 but not in t2, hence it is dropped 907 var tableOption *sqlparser.TableOption 908 switch strings.ToUpper(t1Option.Name) { 909 case "AUTO_INCREMENT": 910 // skip 911 case "AUTOEXTEND_SIZE": 912 // skip 913 case "AVG_ROW_LENGTH": 914 // skip. MyISAM only, not interesting 915 case "CHARSET": 916 switch hints.TableCharsetCollateStrategy { 917 case TableCharsetCollateStrict: 918 tableOption = &sqlparser.TableOption{String: ""} 919 // in all other strategies we ignore the charset 920 } 921 case "CHECKSUM": 922 tableOption = &sqlparser.TableOption{Value: sqlparser.NewIntLiteral("0")} 923 case "COLLATE": 924 // skip. the default collation is applied per CHARSET 925 case "COMMENT": 926 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("")} 927 case "COMPRESSION": 928 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("")} 929 case "CONNECTION": 930 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("")} 931 case "DATA DIRECTORY": 932 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("")} 933 case "DELAY_KEY_WRITE": 934 tableOption = &sqlparser.TableOption{Value: sqlparser.NewIntLiteral("0")} 935 case "ENCRYPTION": 936 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("N")} 937 case "ENGINE": 938 // skip 939 case "ENGINE_ATTRIBUTE": 940 // skip 941 case "INDEX DIRECTORY": 942 tableOption = &sqlparser.TableOption{Value: sqlparser.NewStrLiteral("")} 943 case "INSERT_METHOD": 944 // MyISAM only. skip 945 case "KEY_BLOCK_SIZE": 946 tableOption = &sqlparser.TableOption{Value: sqlparser.NewIntLiteral("0")} 947 case "MAX_ROWS": 948 tableOption = &sqlparser.TableOption{Value: sqlparser.NewIntLiteral("0")} 949 case "MIN_ROWS": 950 tableOption = &sqlparser.TableOption{Value: sqlparser.NewIntLiteral("0")} 951 case "PACK_KEYS": 952 tableOption = &sqlparser.TableOption{String: "DEFAULT"} 953 case "PASSWORD": 954 // unused option. skip 955 case "ROW_FORMAT": 956 tableOption = &sqlparser.TableOption{String: "DEFAULT"} 957 case "SECONDARY_ENGINE_ATTRIBUTE": 958 // unused option. skip 959 case "STATS_AUTO_RECALC": 960 tableOption = &sqlparser.TableOption{String: "DEFAULT"} 961 case "STATS_PERSISTENT": 962 tableOption = &sqlparser.TableOption{String: "DEFAULT"} 963 case "STATS_SAMPLE_PAGES": 964 tableOption = &sqlparser.TableOption{String: "DEFAULT"} 965 case "TABLESPACE": 966 // not supporting the change, skip 967 case "UNION": 968 // MyISAM/MERGE only. Skip 969 default: 970 return &UnsupportedTableOptionError{Table: c.Name(), Option: strings.ToUpper(t1Option.Name)} 971 } 972 if tableOption != nil { 973 tableOption.Name = t1Option.Name 974 alterTableOptions = append(alterTableOptions, tableOption) 975 } 976 } 977 978 } 979 // changed options 980 for _, t2Option := range t2Options { 981 if t1Option, ok := t1OptionsMap[t2Option.Name]; ok { 982 options1 := sqlparser.TableOptions{t1Option} 983 options2 := sqlparser.TableOptions{t2Option} 984 if !sqlparser.Equals.TableOptions(options1, options2) { 985 // options are different. 986 // However, we don't automatically apply these changes. It depends on the option! 987 switch strings.ToUpper(t1Option.Name) { 988 case "CHARSET", "COLLATE": 989 switch hints.TableCharsetCollateStrategy { 990 case TableCharsetCollateStrict: 991 alterTableOptions = append(alterTableOptions, t2Option) 992 case TableCharsetCollateIgnoreEmpty: 993 if t1Option.String != "" && t2Option.String != "" { 994 alterTableOptions = append(alterTableOptions, t2Option) 995 } 996 // if one is empty, we ignore 997 case TableCharsetCollateIgnoreAlways: 998 // ignore always 999 } 1000 case "AUTO_INCREMENT": 1001 switch hints.AutoIncrementStrategy { 1002 case AutoIncrementApplyAlways: 1003 alterTableOptions = append(alterTableOptions, t2Option) 1004 case AutoIncrementApplyHigher: 1005 option1AutoIncrement, err := strconv.ParseInt(t1Option.Value.Val, 10, 64) 1006 if err != nil { 1007 return err 1008 } 1009 option2AutoIncrement, err := strconv.ParseInt(t2Option.Value.Val, 10, 64) 1010 if err != nil { 1011 return err 1012 } 1013 if option2AutoIncrement > option1AutoIncrement { 1014 // never decrease AUTO_INCREMENT. Only increase 1015 alterTableOptions = append(alterTableOptions, t2Option) 1016 } 1017 case AutoIncrementIgnore: 1018 // do not apply 1019 } 1020 default: 1021 // Apply the new options 1022 alterTableOptions = append(alterTableOptions, t2Option) 1023 } 1024 } 1025 } 1026 } 1027 // added options 1028 for _, t2Option := range t2Options { 1029 if _, ok := t1OptionsMap[t2Option.Name]; !ok { 1030 switch strings.ToUpper(t2Option.Name) { 1031 case "CHARSET", "COLLATE": 1032 switch hints.TableCharsetCollateStrategy { 1033 case TableCharsetCollateStrict: 1034 alterTableOptions = append(alterTableOptions, t2Option) 1035 // in all other strategies we ignore the charset 1036 } 1037 case "AUTO_INCREMENT": 1038 switch hints.AutoIncrementStrategy { 1039 case AutoIncrementApplyAlways, AutoIncrementApplyHigher: 1040 alterTableOptions = append(alterTableOptions, t2Option) 1041 case AutoIncrementIgnore: 1042 // do not apply 1043 } 1044 default: 1045 alterTableOptions = append(alterTableOptions, t2Option) 1046 } 1047 } 1048 } 1049 1050 if len(alterTableOptions) > 0 { 1051 alterTable.AlterOptions = append(alterTable.AlterOptions, alterTableOptions) 1052 } 1053 return nil 1054 } 1055 1056 // rangePartitionsAddedRemoved returns true when: 1057 // - both table partitions are RANGE type 1058 // - there is exactly one consequitive non-empty shared sequence of partitions (same names, same range values, in same order) 1059 // - table1 may have non-empty list of partitions _preceding_ this sequence, and table2 may not 1060 // - table2 may have non-empty list of partitions _following_ this sequence, and table1 may not 1061 func (c *CreateTableEntity) isRangePartitionsRotation( 1062 t1Partitions *sqlparser.PartitionOption, 1063 t2Partitions *sqlparser.PartitionOption, 1064 ) (bool, []*sqlparser.PartitionSpec, error) { 1065 // Validate that both tables have range partitioning 1066 if t1Partitions.Type != t2Partitions.Type { 1067 return false, nil, nil 1068 } 1069 if t1Partitions.Type != sqlparser.RangeType { 1070 return false, nil, nil 1071 } 1072 definitions1 := t1Partitions.Definitions 1073 definitions2 := t2Partitions.Definitions 1074 // there has to be a non-empty shared list, therefore both definitions must be non-empty: 1075 if len(definitions1) == 0 { 1076 return false, nil, nil 1077 } 1078 if len(definitions2) == 0 { 1079 return false, nil, nil 1080 } 1081 var droppedPartitions1 []*sqlparser.PartitionDefinition 1082 // It's OK for prefix of t1 partitions to be nonexistent in t2 (as they may have been rotated away in t2) 1083 for len(definitions1) > 0 && !sqlparser.Equals.RefOfPartitionDefinition(definitions1[0], definitions2[0]) { 1084 droppedPartitions1 = append(droppedPartitions1, definitions1[0]) 1085 definitions1 = definitions1[1:] 1086 } 1087 if len(definitions1) == 0 { 1088 // We've exhaused definition1 trying to find a shared partition with definitions2. Nothing found. 1089 // so there is no shared sequence between the two tables. 1090 return false, nil, nil 1091 } 1092 if len(definitions1) > len(definitions2) { 1093 return false, nil, nil 1094 } 1095 // To save computation, and because we've already shown that sqlparser.EqualsRefOfPartitionDefinition(definitions1[0], definitions2[0]), nil, 1096 // we can skip one element 1097 definitions1 = definitions1[1:] 1098 definitions2 = definitions2[1:] 1099 // Now let's ensure that whatever is remaining in definitions1 is an exact match for a prefix of definitions2 1100 // It's ok if we end up with leftover elements in definition2 1101 for len(definitions1) > 0 { 1102 if !sqlparser.Equals.RefOfPartitionDefinition(definitions1[0], definitions2[0]) { 1103 return false, nil, nil 1104 } 1105 definitions1 = definitions1[1:] 1106 definitions2 = definitions2[1:] 1107 } 1108 addedPartitions2 := definitions2 1109 partitionSpecs := make([]*sqlparser.PartitionSpec, 0, len(droppedPartitions1)+len(addedPartitions2)) 1110 for _, p := range droppedPartitions1 { 1111 partitionSpec := &sqlparser.PartitionSpec{ 1112 Action: sqlparser.DropAction, 1113 Names: []sqlparser.IdentifierCI{p.Name}, 1114 } 1115 partitionSpecs = append(partitionSpecs, partitionSpec) 1116 } 1117 for _, p := range addedPartitions2 { 1118 partitionSpec := &sqlparser.PartitionSpec{ 1119 Action: sqlparser.AddAction, 1120 Definitions: []*sqlparser.PartitionDefinition{p}, 1121 } 1122 partitionSpecs = append(partitionSpecs, partitionSpec) 1123 } 1124 return true, partitionSpecs, nil 1125 } 1126 1127 func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, 1128 t1Partitions *sqlparser.PartitionOption, 1129 t2Partitions *sqlparser.PartitionOption, 1130 hints *DiffHints, 1131 ) (partitionSpecs []*sqlparser.PartitionSpec, err error) { 1132 switch { 1133 case t1Partitions == nil && t2Partitions == nil: 1134 return nil, nil 1135 case t1Partitions == nil: 1136 // add partitioning 1137 alterTable.PartitionOption = t2Partitions 1138 case t2Partitions == nil: 1139 // remove partitioning 1140 partitionSpec := &sqlparser.PartitionSpec{ 1141 Action: sqlparser.RemoveAction, 1142 IsAll: true, 1143 } 1144 alterTable.PartitionSpec = partitionSpec 1145 case sqlparser.Equals.RefOfPartitionOption(t1Partitions, t2Partitions): 1146 // identical partitioning 1147 return nil, nil 1148 default: 1149 // partitioning was changed 1150 // For most cases, we produce a complete re-partitioing schema: we don't try and figure out the minimal 1151 // needed change. For example, maybe the minimal change is to REORGANIZE a specific partition and split 1152 // into two, thus unaffecting the rest of the partitions. But we don't evaluate that, we just set a 1153 // complete new ALTER TABLE ... PARTITION BY statement. 1154 // The idea is that it doesn't matter: we're not looking to do optimal in-place ALTERs, we run 1155 // Online DDL alters, where we create a new table anyway. Thus, the optimization is meaningless. 1156 1157 // Having said that, we _do_ analyze the scenario of a RANGE partitioning rotation of partitions: 1158 // where zero or more partitions may have been dropped from the earlier range, and zero or more 1159 // partitions have been added with a later range: 1160 isRotation, partitionSpecs, err := c.isRangePartitionsRotation(t1Partitions, t2Partitions) 1161 if err != nil { 1162 return nil, err 1163 } 1164 if isRotation { 1165 switch hints.RangeRotationStrategy { 1166 case RangeRotationIgnore: 1167 return nil, nil 1168 case RangeRotationDistinctStatements: 1169 return partitionSpecs, nil 1170 case RangeRotationFullSpec: 1171 // proceed to return a full rebuild 1172 } 1173 } 1174 alterTable.PartitionOption = t2Partitions 1175 } 1176 return nil, nil 1177 } 1178 1179 func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, 1180 t1Constraints []*sqlparser.ConstraintDefinition, 1181 t2Constraints []*sqlparser.ConstraintDefinition, 1182 hints *DiffHints, 1183 ) { 1184 normalizeConstraintName := func(constraint *sqlparser.ConstraintDefinition) string { 1185 switch hints.ConstraintNamesStrategy { 1186 case ConstraintNamesIgnoreVitess: 1187 return ExtractConstraintOriginalName(constraint.Name.String()) 1188 case ConstraintNamesIgnoreAll: 1189 return sqlparser.CanonicalString(constraint.Details) 1190 case ConstraintNamesStrict: 1191 return constraint.Name.String() 1192 default: 1193 // should never get here; but while here, let's assume strict. 1194 return constraint.Name.String() 1195 } 1196 } 1197 t1ConstraintsMap := map[string]*sqlparser.ConstraintDefinition{} 1198 t2ConstraintsMap := map[string]*sqlparser.ConstraintDefinition{} 1199 for _, constraint := range t1Constraints { 1200 t1ConstraintsMap[normalizeConstraintName(constraint)] = constraint 1201 } 1202 for _, constraint := range t2Constraints { 1203 t2ConstraintsMap[normalizeConstraintName(constraint)] = constraint 1204 } 1205 1206 dropConstraintStatement := func(constraint *sqlparser.ConstraintDefinition) *sqlparser.DropKey { 1207 if _, fk := constraint.Details.(*sqlparser.ForeignKeyDefinition); fk { 1208 return &sqlparser.DropKey{Name: constraint.Name, Type: sqlparser.ForeignKeyType} 1209 } 1210 return &sqlparser.DropKey{Name: constraint.Name, Type: sqlparser.CheckKeyType} 1211 } 1212 1213 // evaluate dropped constraints 1214 // 1215 for _, t1Constraint := range t1Constraints { 1216 if _, ok := t2ConstraintsMap[normalizeConstraintName(t1Constraint)]; !ok { 1217 // constraint exists in t1 but not in t2, hence it is dropped 1218 dropConstraint := dropConstraintStatement(t1Constraint) 1219 alterTable.AlterOptions = append(alterTable.AlterOptions, dropConstraint) 1220 } 1221 } 1222 1223 for _, t2Constraint := range t2Constraints { 1224 normalizedT2ConstraintName := normalizeConstraintName(t2Constraint) 1225 // evaluate modified & added constraints: 1226 // 1227 if t1Constraint, ok := t1ConstraintsMap[normalizedT2ConstraintName]; ok { 1228 // constraint exists in both tables 1229 // check diff between before/after columns: 1230 if !sqlparser.Equals.ConstraintInfo(t2Constraint.Details, t1Constraint.Details) { 1231 // constraints with same name have different definition. 1232 // First we check if this is only the enforced setting that changed which can 1233 // be directly altered. 1234 check1Details, ok1 := t1Constraint.Details.(*sqlparser.CheckConstraintDefinition) 1235 check2Details, ok2 := t2Constraint.Details.(*sqlparser.CheckConstraintDefinition) 1236 if ok1 && ok2 && sqlparser.Equals.Expr(check1Details.Expr, check2Details.Expr) { 1237 // We have the same expression, so we have a different Enforced here 1238 alterConstraint := &sqlparser.AlterCheck{ 1239 Name: t2Constraint.Name, 1240 Enforced: check2Details.Enforced, 1241 } 1242 alterTable.AlterOptions = append(alterTable.AlterOptions, alterConstraint) 1243 continue 1244 } 1245 1246 // There's another change, so we need to drop and add. 1247 dropConstraint := dropConstraintStatement(t1Constraint) 1248 addConstraint := &sqlparser.AddConstraintDefinition{ 1249 ConstraintDefinition: t2Constraint, 1250 } 1251 alterTable.AlterOptions = append(alterTable.AlterOptions, dropConstraint) 1252 alterTable.AlterOptions = append(alterTable.AlterOptions, addConstraint) 1253 } 1254 } else { 1255 // constraint exists in t2 but not in t1, hence it is added 1256 addConstraint := &sqlparser.AddConstraintDefinition{ 1257 ConstraintDefinition: t2Constraint, 1258 } 1259 alterTable.AlterOptions = append(alterTable.AlterOptions, addConstraint) 1260 } 1261 } 1262 } 1263 1264 func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, 1265 t1Keys []*sqlparser.IndexDefinition, 1266 t2Keys []*sqlparser.IndexDefinition, 1267 hints *DiffHints, 1268 ) (superfluousFulltextKeys []*sqlparser.AddIndexDefinition) { 1269 t1KeysMap := map[string]*sqlparser.IndexDefinition{} 1270 t2KeysMap := map[string]*sqlparser.IndexDefinition{} 1271 for _, key := range t1Keys { 1272 t1KeysMap[key.Info.Name.String()] = key 1273 } 1274 for _, key := range t2Keys { 1275 t2KeysMap[key.Info.Name.String()] = key 1276 } 1277 1278 dropKeyStatement := func(info *sqlparser.IndexInfo) *sqlparser.DropKey { 1279 dropKey := &sqlparser.DropKey{} 1280 if strings.EqualFold(info.Type, sqlparser.PrimaryKeyTypeStr) { 1281 dropKey.Type = sqlparser.PrimaryKeyType 1282 } else { 1283 dropKey.Type = sqlparser.NormalKeyType 1284 dropKey.Name = info.Name 1285 } 1286 return dropKey 1287 } 1288 1289 // evaluate dropped keys 1290 // 1291 for _, t1Key := range t1Keys { 1292 if _, ok := t2KeysMap[t1Key.Info.Name.String()]; !ok { 1293 // column exists in t1 but not in t2, hence it is dropped 1294 dropKey := dropKeyStatement(t1Key.Info) 1295 alterTable.AlterOptions = append(alterTable.AlterOptions, dropKey) 1296 } 1297 } 1298 1299 addedFulltextKeys := 0 1300 for _, t2Key := range t2Keys { 1301 t2KeyName := t2Key.Info.Name.String() 1302 // evaluate modified & added keys: 1303 // 1304 if t1Key, ok := t1KeysMap[t2KeyName]; ok { 1305 // key exists in both tables 1306 // check diff between before/after columns: 1307 if !sqlparser.Equals.RefOfIndexDefinition(t2Key, t1Key) { 1308 indexVisibilityChange, newVisibility := indexOnlyVisibilityChange(t1Key, t2Key) 1309 if indexVisibilityChange { 1310 alterTable.AlterOptions = append(alterTable.AlterOptions, &sqlparser.AlterIndex{ 1311 Name: t2Key.Info.Name, 1312 Invisible: newVisibility, 1313 }) 1314 continue 1315 } 1316 1317 // For other changes, we're going to drop and create. 1318 dropKey := dropKeyStatement(t1Key.Info) 1319 addKey := &sqlparser.AddIndexDefinition{ 1320 IndexDefinition: t2Key, 1321 } 1322 alterTable.AlterOptions = append(alterTable.AlterOptions, dropKey) 1323 alterTable.AlterOptions = append(alterTable.AlterOptions, addKey) 1324 } 1325 } else { 1326 // key exists in t2 but not in t1, hence it is added 1327 addKey := &sqlparser.AddIndexDefinition{ 1328 IndexDefinition: t2Key, 1329 } 1330 addedAsSuperfluousStatement := false 1331 if t2Key.Info.Fulltext { 1332 if addedFulltextKeys > 0 && hints.FullTextKeyStrategy == FullTextKeyDistinctStatements { 1333 // Special case: MySQL does not support multiple ADD FULLTEXT KEY statements in a single ALTER 1334 superfluousFulltextKeys = append(superfluousFulltextKeys, addKey) 1335 addedAsSuperfluousStatement = true 1336 } 1337 addedFulltextKeys++ 1338 } 1339 if !addedAsSuperfluousStatement { 1340 alterTable.AlterOptions = append(alterTable.AlterOptions, addKey) 1341 } 1342 } 1343 } 1344 return superfluousFulltextKeys 1345 } 1346 1347 // indexOnlyVisibilityChange checks whether the change on an index is only 1348 // a visibility change. In that case we can use `ALTER INDEX`. 1349 // Returns if this is a visibility only change and if true, whether 1350 // the new visibility is invisible or not. 1351 func indexOnlyVisibilityChange(t1Key, t2Key *sqlparser.IndexDefinition) (bool, bool) { 1352 t1KeyCopy := sqlparser.CloneRefOfIndexDefinition(t1Key) 1353 t2KeyCopy := sqlparser.CloneRefOfIndexDefinition(t2Key) 1354 t1KeyKeptOptions := make([]*sqlparser.IndexOption, 0, len(t1KeyCopy.Options)) 1355 t2KeyInvisible := false 1356 for _, opt := range t1KeyCopy.Options { 1357 if strings.EqualFold(opt.Name, "invisible") { 1358 continue 1359 } 1360 t1KeyKeptOptions = append(t1KeyKeptOptions, opt) 1361 } 1362 t1KeyCopy.Options = t1KeyKeptOptions 1363 t2KeyKeptOptions := make([]*sqlparser.IndexOption, 0, len(t2KeyCopy.Options)) 1364 for _, opt := range t2KeyCopy.Options { 1365 if strings.EqualFold(opt.Name, "invisible") { 1366 t2KeyInvisible = true 1367 continue 1368 } 1369 t2KeyKeptOptions = append(t2KeyKeptOptions, opt) 1370 } 1371 t2KeyCopy.Options = t2KeyKeptOptions 1372 if sqlparser.Equals.RefOfIndexDefinition(t2KeyCopy, t1KeyCopy) { 1373 return true, t2KeyInvisible 1374 } 1375 return false, false 1376 } 1377 1378 // evaluateColumnReordering produces a minimal reordering set of columns. To elaborate: 1379 // The function receives two sets of columns. the two must be permutations of one another. Specifically, 1380 // these are the columns shared between the from&to tables. 1381 // The function uses longest-common-subsequence (lcs) algorithm to compute which columns should not be moved. 1382 // any column not in the lcs need to be reordered. 1383 // The function a map of column names that need to be reordered, and the index into which they are reordered. 1384 func evaluateColumnReordering(t1SharedColumns, t2SharedColumns []*sqlparser.ColumnDefinition) map[string]int { 1385 minimalColumnReordering := map[string]int{} 1386 1387 t1SharedColNames := make([]interface{}, 0, len(t1SharedColumns)) 1388 for _, col := range t1SharedColumns { 1389 t1SharedColNames = append(t1SharedColNames, col.Name.Lowered()) 1390 } 1391 t2SharedColNames := make([]interface{}, 0, len(t2SharedColumns)) 1392 for _, col := range t2SharedColumns { 1393 t2SharedColNames = append(t2SharedColNames, col.Name.Lowered()) 1394 } 1395 1396 lcs := golcs.New(t1SharedColNames, t2SharedColNames) 1397 lcsNames := map[string]bool{} 1398 for _, v := range lcs.Values() { 1399 lcsNames[v.(string)] = true 1400 } 1401 for i, t2Col := range t2SharedColumns { 1402 t2ColName := t2Col.Name.Lowered() 1403 // see if this column is in the longest common subsequence. If so, no need to reorder it. If not, it must be reordered. 1404 if _, ok := lcsNames[t2ColName]; !ok { 1405 minimalColumnReordering[t2ColName] = i 1406 } 1407 } 1408 1409 return minimalColumnReordering 1410 } 1411 1412 // Diff compares this table statement with another table statement, and sees what it takes to 1413 // change this table to look like the other table. 1414 // It returns an AlterTable statement if changes are found, or nil if not. 1415 // the other table may be of different name; its name is ignored. 1416 func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, 1417 t1Columns []*sqlparser.ColumnDefinition, 1418 t2Columns []*sqlparser.ColumnDefinition, 1419 hints *DiffHints, 1420 tableCharsetChanged bool, 1421 ) { 1422 getColumnsMap := func(cols []*sqlparser.ColumnDefinition) map[string]*columnDetails { 1423 var prevCol *columnDetails 1424 m := map[string]*columnDetails{} 1425 for _, col := range cols { 1426 colDetails := &columnDetails{ 1427 col: col, 1428 prevCol: prevCol, 1429 } 1430 if prevCol != nil { 1431 prevCol.nextCol = colDetails 1432 } 1433 prevCol = colDetails 1434 m[col.Name.Lowered()] = colDetails 1435 } 1436 return m 1437 } 1438 // map columns by names for easy access 1439 t1ColumnsMap := getColumnsMap(t1Columns) 1440 t2ColumnsMap := getColumnsMap(t2Columns) 1441 1442 // For purpose of column reordering detection, we maintain a list of 1443 // shared columns, by order of appearance in t1 1444 var t1SharedColumns []*sqlparser.ColumnDefinition 1445 1446 var dropColumns []*sqlparser.DropColumn 1447 // evaluate dropped columns 1448 // 1449 for _, t1Col := range t1Columns { 1450 if _, ok := t2ColumnsMap[t1Col.Name.Lowered()]; ok { 1451 t1SharedColumns = append(t1SharedColumns, t1Col) 1452 } else { 1453 // column exists in t1 but not in t2, hence it is dropped 1454 dropColumn := &sqlparser.DropColumn{ 1455 Name: getColName(&t1Col.Name), 1456 } 1457 dropColumns = append(dropColumns, dropColumn) 1458 } 1459 } 1460 1461 // For purpose of column reordering detection, we maintain a list of 1462 // shared columns, by order of appearance in t2 1463 var t2SharedColumns []*sqlparser.ColumnDefinition 1464 for _, t2Col := range t2Columns { 1465 if _, ok := t1ColumnsMap[t2Col.Name.Lowered()]; ok { 1466 // column exists in both tables 1467 t2SharedColumns = append(t2SharedColumns, t2Col) 1468 } 1469 } 1470 1471 // evaluate modified columns 1472 // 1473 var modifyColumns []*sqlparser.ModifyColumn 1474 columnReordering := evaluateColumnReordering(t1SharedColumns, t2SharedColumns) 1475 for _, t2Col := range t2SharedColumns { 1476 t2ColName := t2Col.Name.Lowered() 1477 // we know that column exists in both tables 1478 t1Col := t1ColumnsMap[t2ColName] 1479 t1ColEntity := NewColumnDefinitionEntity(t1Col.col) 1480 t2ColEntity := NewColumnDefinitionEntity(t2Col) 1481 1482 // check diff between before/after columns: 1483 modifyColumnDiff := t1ColEntity.ColumnDiff(t2ColEntity, hints) 1484 if modifyColumnDiff == nil { 1485 // even if there's no apparent change, there can still be implicit changes 1486 // it is possible that the table charset is changed. the column may be some col1 TEXT NOT NULL, possibly in both varsions 1 and 2, 1487 // but implicitly the column has changed its characters set. So we need to explicitly ass a MODIFY COLUMN statement, so that 1488 // MySQL rebuilds it. 1489 if tableCharsetChanged && t2ColEntity.IsTextual() && t2Col.Type.Charset.Name == "" { 1490 modifyColumnDiff = NewModifyColumnDiffByDefinition(t2Col) 1491 } 1492 } 1493 // It is also possible that a column is reordered. Whether the column definition has 1494 // or hasn't changed, if a column is reordered then that's a change of its own! 1495 if columnReorderIndex, ok := columnReordering[t2ColName]; ok { 1496 // seems like we previously evaluated that this column should be reordered 1497 if modifyColumnDiff == nil { 1498 // create column change 1499 modifyColumnDiff = NewModifyColumnDiffByDefinition(t2Col) 1500 } 1501 if columnReorderIndex == 0 { 1502 modifyColumnDiff.modifyColumn.First = true 1503 } else { 1504 modifyColumnDiff.modifyColumn.After = getColName(&t2SharedColumns[columnReorderIndex-1].Name) 1505 } 1506 } 1507 if modifyColumnDiff != nil { 1508 // column definition or ordering has changed 1509 modifyColumns = append(modifyColumns, modifyColumnDiff.modifyColumn) 1510 } 1511 } 1512 // Evaluate added columns 1513 // 1514 // Every added column is obviously a diff. But on top of that, we are also interested to know 1515 // if the column is added somewhere in between existing columns rather than appended to the 1516 // end of existing columns list. 1517 var addColumns []*sqlparser.AddColumns 1518 expectAppendIndex := len(t2SharedColumns) 1519 for t2ColIndex, t2Col := range t2Columns { 1520 if _, ok := t1ColumnsMap[t2Col.Name.Lowered()]; !ok { 1521 // column exists in t2 but not in t1, hence it is added 1522 addColumn := &sqlparser.AddColumns{ 1523 Columns: []*sqlparser.ColumnDefinition{t2Col}, 1524 } 1525 if t2ColIndex < expectAppendIndex { 1526 // This column is added somewhere in between existing columns, not appended at end of column list 1527 if t2ColIndex == 0 { 1528 addColumn.First = true 1529 } else { 1530 addColumn.After = getColName(&t2Columns[t2ColIndex-1].Name) 1531 } 1532 } 1533 expectAppendIndex++ 1534 addColumns = append(addColumns, addColumn) 1535 } 1536 } 1537 dropColumns, addColumns, renameColumns := heuristicallyDetectColumnRenames(dropColumns, addColumns, t1ColumnsMap, t2ColumnsMap, hints) 1538 for _, c := range dropColumns { 1539 alterTable.AlterOptions = append(alterTable.AlterOptions, c) 1540 } 1541 for _, c := range modifyColumns { 1542 alterTable.AlterOptions = append(alterTable.AlterOptions, c) 1543 } 1544 for _, c := range renameColumns { 1545 alterTable.AlterOptions = append(alterTable.AlterOptions, c) 1546 } 1547 for _, c := range addColumns { 1548 alterTable.AlterOptions = append(alterTable.AlterOptions, c) 1549 } 1550 } 1551 1552 func heuristicallyDetectColumnRenames( 1553 dropColumns []*sqlparser.DropColumn, 1554 addColumns []*sqlparser.AddColumns, 1555 t1ColumnsMap map[string]*columnDetails, 1556 t2ColumnsMap map[string]*columnDetails, 1557 hints *DiffHints, 1558 ) ([]*sqlparser.DropColumn, []*sqlparser.AddColumns, []*sqlparser.RenameColumn) { 1559 var renameColumns []*sqlparser.RenameColumn 1560 findRenamedColumn := func() bool { 1561 // What we're doing next is to try and identify a column RENAME. 1562 // We do so by cross-referencing dropped and added columns. 1563 // The check is heuristic, and looks like this: 1564 // We consider a column renamed iff: 1565 // - the DROP and ADD column definitions are identical other than the column name, and 1566 // - the DROPped and ADDded column are both FIRST, or they come AFTER the same column, and 1567 // - the DROPped and ADDded column are both last, or they come before the same column 1568 // This v1 chcek therefore cannot handle a case where two successive columns are renamed. 1569 // the problem is complex, and with successive renamed, or drops and adds, it can be 1570 // impossible to tell apart different scenarios. 1571 // At any case, once we heuristically decide that we found a RENAME, we cancel the DROP, 1572 // cancel the ADD, and inject a RENAME in place of both. 1573 1574 // findRenamedColumn cross-references dropped and added columns to find a single renamed column. If such is found: 1575 // we remove the entry from DROPped columns, remove the entry from ADDed columns, add an entry for RENAMEd columns, 1576 // and return 'true'. 1577 // Successive calls to this function will then find the next heuristic RENAMEs. 1578 // the function returns 'false' if it is unable to heuristically find a RENAME. 1579 for iDrop, dropCol1 := range dropColumns { 1580 for iAdd, addCol2 := range addColumns { 1581 col1Details := t1ColumnsMap[dropCol1.Name.Name.Lowered()] 1582 if !col1Details.identicalOtherThanName(addCol2.Columns[0]) { 1583 continue 1584 } 1585 // columns look alike, other than their names, which we know are different. 1586 // are these two columns otherwise appear to be in same position? 1587 col2Details := t2ColumnsMap[addCol2.Columns[0].Name.Lowered()] 1588 if col1Details.prevColName() == col2Details.prevColName() && col1Details.nextColName() == col2Details.nextColName() { 1589 dropColumns = append(dropColumns[0:iDrop], dropColumns[iDrop+1:]...) 1590 addColumns = append(addColumns[0:iAdd], addColumns[iAdd+1:]...) 1591 renameColumn := &sqlparser.RenameColumn{ 1592 OldName: dropCol1.Name, 1593 NewName: getColName(&addCol2.Columns[0].Name), 1594 } 1595 renameColumns = append(renameColumns, renameColumn) 1596 return true 1597 } 1598 } 1599 } 1600 return false 1601 } 1602 switch hints.ColumnRenameStrategy { 1603 case ColumnRenameAssumeDifferent: 1604 // do nothing 1605 case ColumnRenameHeuristicStatement: 1606 for findRenamedColumn() { 1607 // Iteratively detect all RENAMEs 1608 } 1609 } 1610 return dropColumns, addColumns, renameColumns 1611 } 1612 1613 // primaryKeyColumns returns the columns covered by an existing PRIMARY KEY, or nil if there isn't 1614 // a PRIMARY KEY 1615 func (c *CreateTableEntity) primaryKeyColumns() []*sqlparser.IndexColumn { 1616 for _, existingIndex := range c.CreateTable.TableSpec.Indexes { 1617 if existingIndex.Info.Primary { 1618 return existingIndex.Columns 1619 } 1620 } 1621 return nil 1622 } 1623 1624 // Create implements Entity interface 1625 func (c *CreateTableEntity) Create() EntityDiff { 1626 return &CreateTableEntityDiff{to: c, createTable: c.CreateTable} 1627 } 1628 1629 // Drop implements Entity interface 1630 func (c *CreateTableEntity) Drop() EntityDiff { 1631 dropTable := &sqlparser.DropTable{ 1632 FromTables: []sqlparser.TableName{c.Table}, 1633 } 1634 return &DropTableEntityDiff{from: c, dropTable: dropTable} 1635 } 1636 1637 func sortAlterOptions(diff *AlterTableEntityDiff) { 1638 optionOrder := func(opt sqlparser.AlterOption) int { 1639 switch opt.(type) { 1640 case *sqlparser.DropKey: 1641 return 1 1642 case *sqlparser.DropColumn: 1643 return 2 1644 case *sqlparser.ModifyColumn: 1645 return 3 1646 case *sqlparser.RenameColumn: 1647 return 4 1648 case *sqlparser.AddColumns: 1649 return 5 1650 case *sqlparser.AddIndexDefinition: 1651 return 6 1652 case *sqlparser.AddConstraintDefinition: 1653 return 7 1654 case sqlparser.TableOptions, *sqlparser.TableOptions: 1655 return 8 1656 default: 1657 return math.MaxInt 1658 } 1659 } 1660 opts := diff.alterTable.AlterOptions 1661 sort.SliceStable(opts, func(i, j int) bool { 1662 return optionOrder(opts[i]) < optionOrder(opts[j]) 1663 }) 1664 } 1665 1666 // apply attempts to apply an ALTER TABLE diff onto this entity's table definition. 1667 // supported modifications are only those created by schemadiff's Diff() function. 1668 func (c *CreateTableEntity) apply(diff *AlterTableEntityDiff) error { 1669 sortAlterOptions(diff) 1670 // Apply partitioning changes: 1671 if spec := diff.alterTable.PartitionSpec; spec != nil { 1672 switch { 1673 case spec.Action == sqlparser.RemoveAction && spec.IsAll: 1674 // Remove partitioning 1675 c.TableSpec.PartitionOption = nil 1676 case spec.Action == sqlparser.DropAction && len(spec.Names) > 0: 1677 for _, dropPartitionName := range spec.Names { 1678 // Drop partitions 1679 if c.TableSpec.PartitionOption == nil { 1680 return &ApplyPartitionNotFoundError{Table: c.Name(), Partition: dropPartitionName.String()} 1681 } 1682 partitionFound := false 1683 for i, p := range c.TableSpec.PartitionOption.Definitions { 1684 if strings.EqualFold(p.Name.String(), dropPartitionName.String()) { 1685 c.TableSpec.PartitionOption.Definitions = append( 1686 c.TableSpec.PartitionOption.Definitions[0:i], 1687 c.TableSpec.PartitionOption.Definitions[i+1:]..., 1688 ) 1689 partitionFound = true 1690 break 1691 } 1692 } 1693 if !partitionFound { 1694 return &ApplyPartitionNotFoundError{Table: c.Name(), Partition: dropPartitionName.String()} 1695 } 1696 } 1697 case spec.Action == sqlparser.AddAction && len(spec.Definitions) == 1: 1698 // Add one partition 1699 if c.TableSpec.PartitionOption == nil { 1700 return &ApplyNoPartitionsError{Table: c.Name()} 1701 } 1702 if len(c.TableSpec.PartitionOption.Definitions) == 0 { 1703 return &ApplyNoPartitionsError{Table: c.Name()} 1704 } 1705 for _, p := range c.TableSpec.PartitionOption.Definitions { 1706 if strings.EqualFold(p.Name.String(), spec.Definitions[0].Name.String()) { 1707 return &ApplyDuplicatePartitionError{Table: c.Name(), Partition: spec.Definitions[0].Name.String()} 1708 } 1709 } 1710 c.TableSpec.PartitionOption.Definitions = append( 1711 c.TableSpec.PartitionOption.Definitions, 1712 spec.Definitions[0], 1713 ) 1714 default: 1715 return &UnsupportedApplyOperationError{Statement: sqlparser.CanonicalString(spec)} 1716 } 1717 } 1718 if diff.alterTable.PartitionOption != nil { 1719 // Specify new spec: 1720 c.CreateTable.TableSpec.PartitionOption = diff.alterTable.PartitionOption 1721 } 1722 // reorderColumn attempts to reorder column that is right now in position 'colIndex', 1723 // based on its FIRST or AFTER specs (if any) 1724 reorderColumn := func(colIndex int, first bool, after *sqlparser.ColName) error { 1725 var newCols []*sqlparser.ColumnDefinition // nil 1726 col := c.TableSpec.Columns[colIndex] 1727 switch { 1728 case first: 1729 newCols = append(newCols, col) 1730 newCols = append(newCols, c.TableSpec.Columns[0:colIndex]...) 1731 newCols = append(newCols, c.TableSpec.Columns[colIndex+1:]...) 1732 case after != nil: 1733 afterColFound := false 1734 // look for the AFTER column; it has to exist! 1735 for a, afterCol := range c.TableSpec.Columns { 1736 if strings.EqualFold(afterCol.Name.String(), after.Name.String()) { 1737 if colIndex < a { 1738 // moving column i to the right 1739 newCols = append(newCols, c.TableSpec.Columns[0:colIndex]...) 1740 newCols = append(newCols, c.TableSpec.Columns[colIndex+1:a+1]...) 1741 newCols = append(newCols, col) 1742 newCols = append(newCols, c.TableSpec.Columns[a+1:]...) 1743 } else { 1744 // moving column i to the left 1745 newCols = append(newCols, c.TableSpec.Columns[0:a+1]...) 1746 newCols = append(newCols, col) 1747 newCols = append(newCols, c.TableSpec.Columns[a+1:colIndex]...) 1748 newCols = append(newCols, c.TableSpec.Columns[colIndex+1:]...) 1749 } 1750 afterColFound = true 1751 break 1752 } 1753 } 1754 if !afterColFound { 1755 return &ApplyColumnAfterNotFoundError{Table: c.Name(), Column: col.Name.String(), AfterColumn: after.Name.String()} 1756 } 1757 default: 1758 // no change in position 1759 } 1760 1761 if newCols != nil { 1762 c.TableSpec.Columns = newCols 1763 } 1764 return nil 1765 } 1766 1767 columnExists := map[string]bool{} 1768 for _, col := range c.CreateTable.TableSpec.Columns { 1769 columnExists[col.Name.Lowered()] = true 1770 } 1771 1772 // apply a single AlterOption; only supported types are those generated by Diff() 1773 applyAlterOption := func(opt sqlparser.AlterOption) error { 1774 switch opt := opt.(type) { 1775 case *sqlparser.DropKey: 1776 // applies to either indexes or FK constraints 1777 // we expect the named key to be found 1778 found := false 1779 switch opt.Type { 1780 case sqlparser.PrimaryKeyType: 1781 for i, idx := range c.TableSpec.Indexes { 1782 if strings.EqualFold(idx.Info.Type, sqlparser.PrimaryKeyTypeStr) { 1783 found = true 1784 c.TableSpec.Indexes = append(c.TableSpec.Indexes[0:i], c.TableSpec.Indexes[i+1:]...) 1785 break 1786 } 1787 } 1788 case sqlparser.NormalKeyType: 1789 for i, index := range c.TableSpec.Indexes { 1790 if strings.EqualFold(index.Info.Name.String(), opt.Name.String()) { 1791 found = true 1792 c.TableSpec.Indexes = append(c.TableSpec.Indexes[0:i], c.TableSpec.Indexes[i+1:]...) 1793 break 1794 } 1795 } 1796 case sqlparser.ForeignKeyType, sqlparser.CheckKeyType: 1797 for i, constraint := range c.TableSpec.Constraints { 1798 if strings.EqualFold(constraint.Name.String(), opt.Name.String()) { 1799 found = true 1800 c.TableSpec.Constraints = append(c.TableSpec.Constraints[0:i], c.TableSpec.Constraints[i+1:]...) 1801 break 1802 } 1803 } 1804 default: 1805 return &UnsupportedApplyOperationError{Statement: sqlparser.CanonicalString(opt)} 1806 } 1807 if !found { 1808 return &ApplyKeyNotFoundError{Table: c.Name(), Key: opt.Name.String()} 1809 } 1810 1811 // Now, if this is a normal key being dropped, let's validate it does not leave any foreign key constraint uncovered 1812 switch opt.Type { 1813 case sqlparser.PrimaryKeyType, sqlparser.NormalKeyType: 1814 for _, cs := range c.CreateTable.TableSpec.Constraints { 1815 fk, ok := cs.Details.(*sqlparser.ForeignKeyDefinition) 1816 if !ok { 1817 continue 1818 } 1819 if !c.columnsCoveredByInOrderIndex(fk.Source) { 1820 return &IndexNeededByForeignKeyError{Table: c.Name(), Key: opt.Name.String()} 1821 } 1822 } 1823 } 1824 1825 case *sqlparser.AddIndexDefinition: 1826 // validate no existing key by same name 1827 keyName := opt.IndexDefinition.Info.Name.String() 1828 for _, index := range c.TableSpec.Indexes { 1829 if strings.EqualFold(index.Info.Name.String(), keyName) { 1830 return &ApplyDuplicateKeyError{Table: c.Name(), Key: keyName} 1831 } 1832 } 1833 for colName := range getKeyColumnNames(opt.IndexDefinition) { 1834 if !columnExists[colName] { 1835 return &InvalidColumnInKeyError{Table: c.Name(), Column: colName, Key: keyName} 1836 } 1837 } 1838 c.TableSpec.Indexes = append(c.TableSpec.Indexes, opt.IndexDefinition) 1839 case *sqlparser.AddConstraintDefinition: 1840 // validate no existing constraint by same name 1841 for _, cs := range c.TableSpec.Constraints { 1842 if strings.EqualFold(cs.Name.String(), opt.ConstraintDefinition.Name.String()) { 1843 return &ApplyDuplicateConstraintError{Table: c.Name(), Constraint: cs.Name.String()} 1844 } 1845 } 1846 c.TableSpec.Constraints = append(c.TableSpec.Constraints, opt.ConstraintDefinition) 1847 case *sqlparser.AlterCheck: 1848 // we expect the constraint to exist 1849 found := false 1850 for _, constraint := range c.TableSpec.Constraints { 1851 checkDetails, ok := constraint.Details.(*sqlparser.CheckConstraintDefinition) 1852 if ok && strings.EqualFold(constraint.Name.String(), opt.Name.String()) { 1853 found = true 1854 checkDetails.Enforced = opt.Enforced 1855 break 1856 } 1857 } 1858 if !found { 1859 return &ApplyConstraintNotFoundError{Table: c.Name(), Constraint: opt.Name.String()} 1860 } 1861 case *sqlparser.DropColumn: 1862 // we expect the column to exist 1863 found := false 1864 for i, col := range c.TableSpec.Columns { 1865 if strings.EqualFold(col.Name.String(), opt.Name.Name.String()) { 1866 found = true 1867 c.TableSpec.Columns = append(c.TableSpec.Columns[0:i], c.TableSpec.Columns[i+1:]...) 1868 delete(columnExists, col.Name.Lowered()) 1869 break 1870 } 1871 } 1872 if !found { 1873 return &ApplyColumnNotFoundError{Table: c.Name(), Column: opt.Name.Name.String()} 1874 } 1875 case *sqlparser.AddColumns: 1876 if len(opt.Columns) != 1 { 1877 // our Diff only ever generates a single column per AlterOption 1878 return &UnsupportedApplyOperationError{Statement: sqlparser.CanonicalString(opt)} 1879 } 1880 // validate no column by same name 1881 addedCol := opt.Columns[0] 1882 for _, col := range c.TableSpec.Columns { 1883 if strings.EqualFold(col.Name.String(), addedCol.Name.String()) { 1884 return &ApplyDuplicateColumnError{Table: c.Name(), Column: addedCol.Name.String()} 1885 } 1886 } 1887 // if this column has the PRIMARY KEY option, verify there isn't already a PRIMARY KEY 1888 if addedCol.Type.Options.KeyOpt == sqlparser.ColKeyPrimary { 1889 if cols := c.primaryKeyColumns(); cols != nil { 1890 return &DuplicateKeyNameError{Table: c.Name(), Key: "PRIMARY"} 1891 } 1892 } 1893 c.TableSpec.Columns = append(c.TableSpec.Columns, addedCol) 1894 // see if we need to position it anywhere other than end of table 1895 if err := reorderColumn(len(c.TableSpec.Columns)-1, opt.First, opt.After); err != nil { 1896 return err 1897 } 1898 columnExists[addedCol.Name.Lowered()] = true 1899 case *sqlparser.ModifyColumn: 1900 // we expect the column to exist 1901 found := false 1902 for i, col := range c.TableSpec.Columns { 1903 if strings.EqualFold(col.Name.String(), opt.NewColDefinition.Name.String()) { 1904 found = true 1905 // redefine. see if we need to position it anywhere other than end of table 1906 c.TableSpec.Columns[i] = opt.NewColDefinition 1907 if err := reorderColumn(i, opt.First, opt.After); err != nil { 1908 return err 1909 } 1910 break 1911 } 1912 } 1913 if !found { 1914 return &ApplyColumnNotFoundError{Table: c.Name(), Column: opt.NewColDefinition.Name.String()} 1915 } 1916 // if this column has the PRIMARY KEY option: 1917 // - validate there isn't already a PRIMARY KEY for other columns 1918 // - if there isn't any PRIMARY KEY, create one 1919 // - if there exists a PRIMARY KEY for exactly this column, noop 1920 if opt.NewColDefinition.Type.Options.KeyOpt == sqlparser.ColKeyPrimary { 1921 cols := c.primaryKeyColumns() 1922 if cols == nil { 1923 // add primary key 1924 c.CreateTable.TableSpec.Indexes = append([]*sqlparser.IndexDefinition{newPrimaryKeyIndexDefinitionSingleColumn(opt.NewColDefinition.Name)}, c.CreateTable.TableSpec.Indexes...) 1925 } else { 1926 if len(cols) == 1 && strings.EqualFold(cols[0].Column.String(), opt.NewColDefinition.Name.String()) { 1927 // existing PK is exactly this column. Nothing to do 1928 } else { 1929 return &DuplicateKeyNameError{Table: c.Name(), Key: "PRIMARY"} 1930 } 1931 } 1932 } 1933 opt.NewColDefinition.Type.Options.KeyOpt = sqlparser.ColKeyNone 1934 case *sqlparser.RenameColumn: 1935 // we expect the column to exist 1936 found := false 1937 for i, col := range c.TableSpec.Columns { 1938 if strings.EqualFold(col.Name.String(), opt.OldName.Name.String()) { 1939 found = true 1940 // redefine. see if we need to position it anywhere other than end of table 1941 c.TableSpec.Columns[i].Name = opt.NewName.Name 1942 delete(columnExists, opt.OldName.Name.Lowered()) 1943 columnExists[opt.NewName.Name.Lowered()] = true 1944 break 1945 } 1946 } 1947 if !found { 1948 return &ApplyColumnNotFoundError{Table: c.Name(), Column: opt.OldName.Name.String()} 1949 } 1950 case *sqlparser.AlterColumn: 1951 // we expect the column to exist 1952 found := false 1953 for _, col := range c.TableSpec.Columns { 1954 if strings.EqualFold(col.Name.String(), opt.Column.Name.String()) { 1955 found = true 1956 if opt.DropDefault { 1957 col.Type.Options.Default = nil 1958 } else if opt.DefaultVal != nil { 1959 col.Type.Options.Default = opt.DefaultVal 1960 } 1961 col.Type.Options.Invisible = opt.Invisible 1962 break 1963 } 1964 } 1965 if !found { 1966 return &ApplyColumnNotFoundError{Table: c.Name(), Column: opt.Column.Name.String()} 1967 } 1968 case *sqlparser.AlterIndex: 1969 // we expect the index to exist 1970 found := false 1971 for _, idx := range c.TableSpec.Indexes { 1972 if strings.EqualFold(idx.Info.Name.String(), opt.Name.String()) { 1973 found = true 1974 if opt.Invisible { 1975 idx.Options = append(idx.Options, &sqlparser.IndexOption{Name: "invisible"}) 1976 } else { 1977 keptOptions := make([]*sqlparser.IndexOption, 0, len(idx.Options)) 1978 for _, idxOpt := range idx.Options { 1979 if strings.EqualFold(idxOpt.Name, "invisible") { 1980 continue 1981 } 1982 keptOptions = append(keptOptions, idxOpt) 1983 } 1984 idx.Options = keptOptions 1985 } 1986 break 1987 } 1988 } 1989 if !found { 1990 return &ApplyKeyNotFoundError{Table: c.Name(), Key: opt.Name.String()} 1991 } 1992 case sqlparser.TableOptions: 1993 // Apply table options. Options that have their DEFAULT value are actually removed. 1994 for _, option := range opt { 1995 func() { 1996 for i, existingOption := range c.TableSpec.Options { 1997 if strings.EqualFold(option.Name, existingOption.Name) { 1998 if isDefaultTableOptionValue(option) { 1999 // remove the option 2000 c.TableSpec.Options = append(c.TableSpec.Options[0:i], c.TableSpec.Options[i+1:]...) 2001 } else { 2002 c.TableSpec.Options[i] = option 2003 } 2004 // option found. No need for further iteration. 2005 return 2006 } 2007 } 2008 // option not found. We add it 2009 c.TableSpec.Options = append(c.TableSpec.Options, option) 2010 }() 2011 } 2012 case sqlparser.AlgorithmValue: 2013 // silently ignore. This has an operational effect on the MySQL engine, but has no semantical effect. 2014 default: 2015 return &UnsupportedApplyOperationError{Statement: sqlparser.CanonicalString(opt)} 2016 } 2017 return nil 2018 } 2019 for _, alterOption := range diff.alterTable.AlterOptions { 2020 if err := applyAlterOption(alterOption); err != nil { 2021 return err 2022 } 2023 } 2024 if err := c.postApplyNormalize(); err != nil { 2025 return err 2026 } 2027 if err := c.validate(); err != nil { 2028 return err 2029 } 2030 return nil 2031 } 2032 2033 // Apply attempts to apply given ALTER TABLE diff onto the table defined by this entity. 2034 // This entity is unmodified. If successful, a new CREATE TABLE entity is returned. 2035 func (c *CreateTableEntity) Apply(diff EntityDiff) (Entity, error) { 2036 dup := c.Clone().(*CreateTableEntity) 2037 for diff != nil { 2038 alterDiff, ok := diff.(*AlterTableEntityDiff) 2039 if !ok { 2040 return nil, ErrEntityTypeMismatch 2041 } 2042 if !diff.IsEmpty() { 2043 if err := dup.apply(alterDiff); err != nil { 2044 return nil, err 2045 } 2046 } 2047 diff = diff.SubsequentDiff() 2048 } 2049 // Always normalize after an Apply to get consistent AST structures. 2050 dup.normalize() 2051 return dup, nil 2052 } 2053 2054 // postApplyNormalize runs at the end of apply() and to reorganize/edit things that 2055 // a MySQL will do implicitly: 2056 // - edit or remove keys if referenced columns are dropped 2057 // - drop check constraints for a single specific column if that column 2058 // is the only referenced column in that check constraint. 2059 // - add implicit keys for foreign key constraint, if needed 2060 func (c *CreateTableEntity) postApplyNormalize() error { 2061 // reduce or remove keys based on existing column list 2062 // (a column may have been removed)postApplyNormalize 2063 columnExists := map[string]bool{} 2064 for _, col := range c.CreateTable.TableSpec.Columns { 2065 columnExists[col.Name.Lowered()] = true 2066 } 2067 var nonEmptyIndexes []*sqlparser.IndexDefinition 2068 2069 keyHasNonExistentColumns := func(keyCol *sqlparser.IndexColumn) bool { 2070 if keyCol.Column.Lowered() != "" { 2071 if !columnExists[keyCol.Column.Lowered()] { 2072 return true 2073 } 2074 } 2075 return false 2076 } 2077 for _, key := range c.CreateTable.TableSpec.Indexes { 2078 var existingKeyColumns []*sqlparser.IndexColumn 2079 for _, keyCol := range key.Columns { 2080 if !keyHasNonExistentColumns(keyCol) { 2081 existingKeyColumns = append(existingKeyColumns, keyCol) 2082 } 2083 } 2084 if len(existingKeyColumns) > 0 { 2085 key.Columns = existingKeyColumns 2086 nonEmptyIndexes = append(nonEmptyIndexes, key) 2087 } 2088 } 2089 c.CreateTable.TableSpec.Indexes = nonEmptyIndexes 2090 2091 var keptConstraints []*sqlparser.ConstraintDefinition 2092 for _, constraint := range c.CreateTable.TableSpec.Constraints { 2093 check, ok := constraint.Details.(*sqlparser.CheckConstraintDefinition) 2094 if !ok { 2095 keptConstraints = append(keptConstraints, constraint) 2096 continue 2097 } 2098 var referencedColumns []string 2099 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2100 switch node := node.(type) { 2101 case *sqlparser.ColName: 2102 referencedColumns = append(referencedColumns, node.Name.String()) 2103 } 2104 return true, nil 2105 }, check.Expr) 2106 if err != nil { 2107 return err 2108 } 2109 if len(referencedColumns) != 1 { 2110 keptConstraints = append(keptConstraints, constraint) 2111 continue 2112 } 2113 2114 referencedColumn := referencedColumns[0] 2115 if columnExists[strings.ToLower(referencedColumn)] { 2116 keptConstraints = append(keptConstraints, constraint) 2117 } 2118 } 2119 c.CreateTable.TableSpec.Constraints = keptConstraints 2120 2121 c.normalizePrimaryKeyColumns() 2122 c.normalizeForeignKeyIndexes() 2123 c.normalizeKeys() 2124 2125 return nil 2126 } 2127 2128 func getNodeColumns(node sqlparser.SQLNode) (colNames map[string]bool) { 2129 colNames = map[string]bool{} 2130 if node != nil { 2131 _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2132 switch node := node.(type) { 2133 case *sqlparser.ColName: 2134 colNames[node.Name.Lowered()] = true 2135 } 2136 return true, nil 2137 }, node) 2138 } 2139 return colNames 2140 } 2141 2142 func getKeyColumnNames(key *sqlparser.IndexDefinition) (colNames map[string]bool) { 2143 colNames = map[string]bool{} 2144 for _, col := range key.Columns { 2145 if colName := col.Column.Lowered(); colName != "" { 2146 colNames[colName] = true 2147 } 2148 for name := range getNodeColumns(col.Expression) { 2149 colNames[name] = true 2150 } 2151 } 2152 return colNames 2153 } 2154 2155 // indexCoversColumnsInOrder checks if the given index covers the given columns in order and in prefix. 2156 // the index must either covers the exact list of columns or continue to cover additional columns beyond. 2157 // Used for validating indexes covering foreign keys. 2158 func indexCoversColumnsInOrder(index *sqlparser.IndexDefinition, columns sqlparser.Columns) bool { 2159 if len(columns) == 0 { 2160 return false 2161 } 2162 if len(index.Columns) < len(columns) { 2163 // obviously the index doesn't cover the required columns 2164 return false 2165 } 2166 for i, col := range columns { 2167 // the index must cover same columns, in order, wih possibly more columns covered than requested. 2168 indexCol := index.Columns[i] 2169 if !strings.EqualFold(col.String(), indexCol.Column.String()) { 2170 return false 2171 } 2172 } 2173 return true 2174 } 2175 2176 // indexesCoveringForeignKeyColumns returns a list of indexes that cover a given list of coumns, in-oder and in prefix. 2177 // Used for validating indexes covering foreign keys. 2178 func (c *CreateTableEntity) indexesCoveringForeignKeyColumns(columns sqlparser.Columns) (indexes []*sqlparser.IndexDefinition) { 2179 for _, index := range c.CreateTable.TableSpec.Indexes { 2180 if indexCoversColumnsInOrder(index, columns) { 2181 indexes = append(indexes, index) 2182 } 2183 } 2184 return indexes 2185 } 2186 2187 // columnsCoveredByInOrderIndex returns 'true' when there is at least one index that covers the given 2188 // list of columns in-order and in-prefix. 2189 func (c *CreateTableEntity) columnsCoveredByInOrderIndex(columns sqlparser.Columns) bool { 2190 return len(c.indexesCoveringForeignKeyColumns(columns)) > 0 2191 } 2192 2193 func (c *CreateTableEntity) validateDuplicateKeyNameError() error { 2194 keyNames := map[string]bool{} 2195 for _, key := range c.CreateTable.TableSpec.Indexes { 2196 name := key.Info.Name 2197 if _, ok := keyNames[name.Lowered()]; ok { 2198 return &DuplicateKeyNameError{Table: c.Name(), Key: name.String()} 2199 } 2200 keyNames[name.Lowered()] = true 2201 } 2202 return nil 2203 } 2204 2205 // validate checks that the table structure is valid: 2206 // - all columns referenced by keys exist 2207 func (c *CreateTableEntity) validate() error { 2208 columnExists := map[string]bool{} 2209 for _, col := range c.CreateTable.TableSpec.Columns { 2210 colName := col.Name.Lowered() 2211 if columnExists[colName] { 2212 return &ApplyDuplicateColumnError{Table: c.Name(), Column: col.Name.String()} 2213 } 2214 columnExists[colName] = true 2215 } 2216 // validate all columns used by foreign key constraints do in fact exist, 2217 // and that there exists an index over those columns 2218 for _, cs := range c.CreateTable.TableSpec.Constraints { 2219 fk, ok := cs.Details.(*sqlparser.ForeignKeyDefinition) 2220 if !ok { 2221 continue 2222 } 2223 if len(fk.Source) != len(fk.ReferenceDefinition.ReferencedColumns) { 2224 return &ForeignKeyColumnCountMismatchError{Table: c.Name(), Constraint: cs.Name.String(), ColumnCount: len(fk.Source), ReferencedTable: fk.ReferenceDefinition.ReferencedTable.Name.String(), ReferencedColumnCount: len(fk.ReferenceDefinition.ReferencedColumns)} 2225 } 2226 for _, col := range fk.Source { 2227 if !columnExists[col.Lowered()] { 2228 return &InvalidColumnInForeignKeyConstraintError{Table: c.Name(), Constraint: cs.Name.String(), Column: col.String()} 2229 } 2230 } 2231 } 2232 // validate all columns referenced by indexes do in fact exist 2233 for _, key := range c.CreateTable.TableSpec.Indexes { 2234 for colName := range getKeyColumnNames(key) { 2235 if !columnExists[colName] { 2236 return &InvalidColumnInKeyError{Table: c.Name(), Column: colName, Key: key.Info.Name.String()} 2237 } 2238 } 2239 } 2240 // validate all columns referenced by generated columns do in fact exist 2241 for _, col := range c.CreateTable.TableSpec.Columns { 2242 if col.Type.Options != nil && col.Type.Options.As != nil { 2243 var referencedColumns []string 2244 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2245 switch node := node.(type) { 2246 case *sqlparser.ColName: 2247 referencedColumns = append(referencedColumns, node.Name.String()) 2248 } 2249 return true, nil 2250 }, col.Type.Options.As) 2251 if err != nil { 2252 return err 2253 } 2254 for _, referencedColName := range referencedColumns { 2255 if !columnExists[strings.ToLower(referencedColName)] { 2256 return &InvalidColumnInGeneratedColumnError{Table: c.Name(), Column: referencedColName, GeneratedColumn: col.Name.String()} 2257 } 2258 } 2259 } 2260 } 2261 // validate all columns referenced by functional indexes do in fact exist 2262 for _, idx := range c.CreateTable.TableSpec.Indexes { 2263 for _, idxCol := range idx.Columns { 2264 var referencedColumns []string 2265 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2266 switch node := node.(type) { 2267 case *sqlparser.ColName: 2268 referencedColumns = append(referencedColumns, node.Name.String()) 2269 } 2270 return true, nil 2271 }, idxCol.Expression) 2272 if err != nil { 2273 return err 2274 } 2275 for _, referencedColName := range referencedColumns { 2276 if !columnExists[strings.ToLower(referencedColName)] { 2277 return &InvalidColumnInKeyError{Table: c.Name(), Column: referencedColName, Key: idx.Info.Name.String()} 2278 } 2279 } 2280 } 2281 } 2282 // validate all columns referenced by constraint checks do in fact exist 2283 for _, cs := range c.CreateTable.TableSpec.Constraints { 2284 check, ok := cs.Details.(*sqlparser.CheckConstraintDefinition) 2285 if !ok { 2286 continue 2287 } 2288 var referencedColumns []string 2289 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2290 switch node := node.(type) { 2291 case *sqlparser.ColName: 2292 referencedColumns = append(referencedColumns, node.Name.String()) 2293 } 2294 return true, nil 2295 }, check.Expr) 2296 if err != nil { 2297 return err 2298 } 2299 for _, referencedColName := range referencedColumns { 2300 if !columnExists[strings.ToLower(referencedColName)] { 2301 return &InvalidColumnInCheckConstraintError{Table: c.Name(), Constraint: cs.Name.String(), Column: referencedColName} 2302 } 2303 } 2304 } 2305 // validate no two keys have same name 2306 if err := c.validateDuplicateKeyNameError(); err != nil { 2307 return err 2308 } 2309 2310 if partition := c.CreateTable.TableSpec.PartitionOption; partition != nil { 2311 // validate no two partitions have same name 2312 partitionExists := map[string]bool{} 2313 for _, p := range partition.Definitions { 2314 if partitionExists[p.Name.Lowered()] { 2315 return &ApplyDuplicatePartitionError{Table: c.Name(), Partition: p.Name.String()} 2316 } 2317 partitionExists[p.Name.Lowered()] = true 2318 } 2319 // validate columns referenced by partitions do in fact exist 2320 // also, validate that all unique keys include partitioned columns 2321 var partitionColNames []string 2322 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 2323 switch node := node.(type) { 2324 case *sqlparser.ColName: 2325 partitionColNames = append(partitionColNames, node.Name.String()) 2326 } 2327 return true, nil 2328 }, partition.Expr) 2329 if err != nil { 2330 return err 2331 } 2332 2333 for _, partitionColName := range partitionColNames { 2334 // Validate columns exists in table: 2335 if !columnExists[strings.ToLower(partitionColName)] { 2336 return &InvalidColumnInPartitionError{Table: c.Name(), Column: partitionColName} 2337 } 2338 2339 // Validate all unique keys include this column: 2340 for _, key := range c.CreateTable.TableSpec.Indexes { 2341 if !key.Info.Unique { 2342 continue 2343 } 2344 colFound := false 2345 for _, col := range key.Columns { 2346 if strings.EqualFold(col.Column.String(), partitionColName) { 2347 colFound = true 2348 break 2349 } 2350 } 2351 if !colFound { 2352 return &MissingPartitionColumnInUniqueKeyError{Table: c.Name(), Column: partitionColName, UniqueKey: key.Info.Name.String()} 2353 } 2354 } 2355 } 2356 } 2357 return nil 2358 } 2359 2360 // identicalOtherThanName returns true when this CREATE TABLE and the given one, are identical 2361 // other than in table's name. We assume both have been normalized. 2362 func (c *CreateTableEntity) identicalOtherThanName(other *CreateTableEntity) bool { 2363 if other == nil { 2364 return false 2365 } 2366 return sqlparser.Equals.RefOfTableSpec(c.TableSpec, other.TableSpec) && 2367 sqlparser.Equals.RefOfParsedComments(c.Comments, other.Comments) 2368 }