github.com/systematiccaos/gorm@v1.22.6/migrator/migrator.go (about) 1 package migrator 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "reflect" 8 "regexp" 9 "strings" 10 11 "github.com/systematiccaos/gorm" 12 "github.com/systematiccaos/gorm/clause" 13 "github.com/systematiccaos/gorm/schema" 14 ) 15 16 var ( 17 regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`) 18 regFullDataType = regexp.MustCompile(`[^\d]*(\d+)[^\d]?`) 19 ) 20 21 // Migrator m struct 22 type Migrator struct { 23 Config 24 } 25 26 // Config schema config 27 type Config struct { 28 CreateIndexAfterCreateTable bool 29 DB *gorm.DB 30 gorm.Dialector 31 } 32 33 type GormDataTypeInterface interface { 34 GormDBDataType(*gorm.DB, *schema.Field) string 35 } 36 37 func (m Migrator) RunWithValue(value interface{}, fc func(*gorm.Statement) error) error { 38 stmt := &gorm.Statement{DB: m.DB} 39 if m.DB.Statement != nil { 40 stmt.Table = m.DB.Statement.Table 41 stmt.TableExpr = m.DB.Statement.TableExpr 42 } 43 44 if table, ok := value.(string); ok { 45 stmt.Table = table 46 } else if err := stmt.ParseWithSpecialTableName(value, stmt.Table); err != nil { 47 return err 48 } 49 50 return fc(stmt) 51 } 52 53 func (m Migrator) DataTypeOf(field *schema.Field) string { 54 fieldValue := reflect.New(field.IndirectFieldType) 55 if dataTyper, ok := fieldValue.Interface().(GormDataTypeInterface); ok { 56 if dataType := dataTyper.GormDBDataType(m.DB, field); dataType != "" { 57 return dataType 58 } 59 } 60 61 return m.Dialector.DataTypeOf(field) 62 } 63 64 func (m Migrator) FullDataTypeOf(field *schema.Field) (expr clause.Expr) { 65 expr.SQL = m.DataTypeOf(field) 66 67 if field.NotNull { 68 expr.SQL += " NOT NULL" 69 } 70 71 if field.Unique { 72 expr.SQL += " UNIQUE" 73 } 74 75 if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") { 76 if field.DefaultValueInterface != nil { 77 defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}} 78 m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface) 79 expr.SQL += " DEFAULT " + m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface) 80 } else if field.DefaultValue != "(-)" { 81 expr.SQL += " DEFAULT " + field.DefaultValue 82 } 83 } 84 85 return 86 } 87 88 // AutoMigrate 89 func (m Migrator) AutoMigrate(values ...interface{}) error { 90 for _, value := range m.ReorderModels(values, true) { 91 tx := m.DB.Session(&gorm.Session{}) 92 if !tx.Migrator().HasTable(value) { 93 if err := tx.Migrator().CreateTable(value); err != nil { 94 return err 95 } 96 } else { 97 if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) { 98 columnTypes, _ := m.DB.Migrator().ColumnTypes(value) 99 100 for _, field := range stmt.Schema.FieldsByDBName { 101 var foundColumn gorm.ColumnType 102 103 for _, columnType := range columnTypes { 104 if columnType.Name() == field.DBName { 105 foundColumn = columnType 106 break 107 } 108 } 109 110 if foundColumn == nil { 111 // not found, add column 112 if err := tx.Migrator().AddColumn(value, field.DBName); err != nil { 113 return err 114 } 115 } else if err := m.DB.Migrator().MigrateColumn(value, field, foundColumn); err != nil { 116 // found, smart migrate 117 return err 118 } 119 } 120 121 for _, rel := range stmt.Schema.Relationships.Relations { 122 if !m.DB.Config.DisableForeignKeyConstraintWhenMigrating { 123 if constraint := rel.ParseConstraint(); constraint != nil && 124 constraint.Schema == stmt.Schema && !tx.Migrator().HasConstraint(value, constraint.Name) { 125 if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil { 126 return err 127 } 128 } 129 } 130 131 for _, chk := range stmt.Schema.ParseCheckConstraints() { 132 if !tx.Migrator().HasConstraint(value, chk.Name) { 133 if err := tx.Migrator().CreateConstraint(value, chk.Name); err != nil { 134 return err 135 } 136 } 137 } 138 } 139 140 for _, idx := range stmt.Schema.ParseIndexes() { 141 if !tx.Migrator().HasIndex(value, idx.Name) { 142 if err := tx.Migrator().CreateIndex(value, idx.Name); err != nil { 143 return err 144 } 145 } 146 } 147 148 return nil 149 }); err != nil { 150 return err 151 } 152 } 153 } 154 155 return nil 156 } 157 158 func (m Migrator) GetTables() (tableList []string, err error) { 159 err = m.DB.Raw("SELECT TABLE_NAME FROM information_schema.tables where TABLE_SCHEMA=?", m.CurrentDatabase()). 160 Scan(&tableList).Error 161 return 162 } 163 164 func (m Migrator) CreateTable(values ...interface{}) error { 165 for _, value := range m.ReorderModels(values, false) { 166 tx := m.DB.Session(&gorm.Session{}) 167 if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) { 168 var ( 169 createTableSQL = "CREATE TABLE ? (" 170 values = []interface{}{m.CurrentTable(stmt)} 171 hasPrimaryKeyInDataType bool 172 ) 173 174 for _, dbName := range stmt.Schema.DBNames { 175 field := stmt.Schema.FieldsByDBName[dbName] 176 if !field.IgnoreMigration { 177 createTableSQL += "? ?" 178 hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(string(field.DataType)), "PRIMARY KEY") 179 values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field)) 180 createTableSQL += "," 181 } 182 } 183 184 if !hasPrimaryKeyInDataType && len(stmt.Schema.PrimaryFields) > 0 { 185 createTableSQL += "PRIMARY KEY ?," 186 primaryKeys := []interface{}{} 187 for _, field := range stmt.Schema.PrimaryFields { 188 primaryKeys = append(primaryKeys, clause.Column{Name: field.DBName}) 189 } 190 191 values = append(values, primaryKeys) 192 } 193 194 for _, idx := range stmt.Schema.ParseIndexes() { 195 if m.CreateIndexAfterCreateTable { 196 defer func(value interface{}, name string) { 197 if errr == nil { 198 errr = tx.Migrator().CreateIndex(value, name) 199 } 200 }(value, idx.Name) 201 } else { 202 if idx.Class != "" { 203 createTableSQL += idx.Class + " " 204 } 205 createTableSQL += "INDEX ? ?" 206 207 if idx.Comment != "" { 208 createTableSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment) 209 } 210 211 if idx.Option != "" { 212 createTableSQL += " " + idx.Option 213 } 214 215 createTableSQL += "," 216 values = append(values, clause.Expr{SQL: idx.Name}, tx.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt)) 217 } 218 } 219 220 for _, rel := range stmt.Schema.Relationships.Relations { 221 if !m.DB.DisableForeignKeyConstraintWhenMigrating { 222 if constraint := rel.ParseConstraint(); constraint != nil { 223 if constraint.Schema == stmt.Schema { 224 sql, vars := buildConstraint(constraint) 225 createTableSQL += sql + "," 226 values = append(values, vars...) 227 } 228 } 229 } 230 } 231 232 for _, chk := range stmt.Schema.ParseCheckConstraints() { 233 createTableSQL += "CONSTRAINT ? CHECK (?)," 234 values = append(values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}) 235 } 236 237 createTableSQL = strings.TrimSuffix(createTableSQL, ",") 238 239 createTableSQL += ")" 240 241 if tableOption, ok := m.DB.Get("gorm:table_options"); ok { 242 createTableSQL += fmt.Sprint(tableOption) 243 } 244 245 errr = tx.Exec(createTableSQL, values...).Error 246 return errr 247 }); err != nil { 248 return err 249 } 250 } 251 return nil 252 } 253 254 func (m Migrator) DropTable(values ...interface{}) error { 255 values = m.ReorderModels(values, false) 256 for i := len(values) - 1; i >= 0; i-- { 257 tx := m.DB.Session(&gorm.Session{}) 258 if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error { 259 return tx.Exec("DROP TABLE IF EXISTS ?", m.CurrentTable(stmt)).Error 260 }); err != nil { 261 return err 262 } 263 } 264 return nil 265 } 266 267 func (m Migrator) HasTable(value interface{}) bool { 268 var count int64 269 270 m.RunWithValue(value, func(stmt *gorm.Statement) error { 271 currentDatabase := m.DB.Migrator().CurrentDatabase() 272 return m.DB.Raw("SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?", currentDatabase, stmt.Table, "BASE TABLE").Row().Scan(&count) 273 }) 274 275 return count > 0 276 } 277 278 func (m Migrator) RenameTable(oldName, newName interface{}) error { 279 var oldTable, newTable interface{} 280 if v, ok := oldName.(string); ok { 281 oldTable = clause.Table{Name: v} 282 } else { 283 stmt := &gorm.Statement{DB: m.DB} 284 if err := stmt.Parse(oldName); err == nil { 285 oldTable = m.CurrentTable(stmt) 286 } else { 287 return err 288 } 289 } 290 291 if v, ok := newName.(string); ok { 292 newTable = clause.Table{Name: v} 293 } else { 294 stmt := &gorm.Statement{DB: m.DB} 295 if err := stmt.Parse(newName); err == nil { 296 newTable = m.CurrentTable(stmt) 297 } else { 298 return err 299 } 300 } 301 302 return m.DB.Exec("ALTER TABLE ? RENAME TO ?", oldTable, newTable).Error 303 } 304 305 func (m Migrator) AddColumn(value interface{}, field string) error { 306 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 307 // avoid using the same name field 308 f := stmt.Schema.LookUpField(field) 309 if f == nil { 310 return fmt.Errorf("failed to look up field with name: %s", field) 311 } 312 313 if !f.IgnoreMigration { 314 return m.DB.Exec( 315 "ALTER TABLE ? ADD ? ?", 316 m.CurrentTable(stmt), clause.Column{Name: f.DBName}, m.DB.Migrator().FullDataTypeOf(f), 317 ).Error 318 } 319 320 return nil 321 }) 322 } 323 324 func (m Migrator) DropColumn(value interface{}, name string) error { 325 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 326 if field := stmt.Schema.LookUpField(name); field != nil { 327 name = field.DBName 328 } 329 330 return m.DB.Exec( 331 "ALTER TABLE ? DROP COLUMN ?", m.CurrentTable(stmt), clause.Column{Name: name}, 332 ).Error 333 }) 334 } 335 336 func (m Migrator) AlterColumn(value interface{}, field string) error { 337 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 338 if field := stmt.Schema.LookUpField(field); field != nil { 339 fileType := clause.Expr{SQL: m.DataTypeOf(field)} 340 return m.DB.Exec( 341 "ALTER TABLE ? ALTER COLUMN ? TYPE ?", 342 m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fileType, 343 ).Error 344 345 } 346 return fmt.Errorf("failed to look up field with name: %s", field) 347 }) 348 } 349 350 func (m Migrator) HasColumn(value interface{}, field string) bool { 351 var count int64 352 m.RunWithValue(value, func(stmt *gorm.Statement) error { 353 currentDatabase := m.DB.Migrator().CurrentDatabase() 354 name := field 355 if field := stmt.Schema.LookUpField(field); field != nil { 356 name = field.DBName 357 } 358 359 return m.DB.Raw( 360 "SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?", 361 currentDatabase, stmt.Table, name, 362 ).Row().Scan(&count) 363 }) 364 365 return count > 0 366 } 367 368 func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error { 369 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 370 if field := stmt.Schema.LookUpField(oldName); field != nil { 371 oldName = field.DBName 372 } 373 374 if field := stmt.Schema.LookUpField(newName); field != nil { 375 newName = field.DBName 376 } 377 378 return m.DB.Exec( 379 "ALTER TABLE ? RENAME COLUMN ? TO ?", 380 m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName}, 381 ).Error 382 }) 383 } 384 385 func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error { 386 // found, smart migrate 387 fullDataType := strings.ToLower(m.DB.Migrator().FullDataTypeOf(field).SQL) 388 realDataType := strings.ToLower(columnType.DatabaseTypeName()) 389 390 alterColumn := false 391 392 // check size 393 if length, ok := columnType.Length(); length != int64(field.Size) { 394 if length > 0 && field.Size > 0 { 395 alterColumn = true 396 } else { 397 // has size in data type and not equal 398 // Since the following code is frequently called in the for loop, reg optimization is needed here 399 matches := regRealDataType.FindAllStringSubmatch(realDataType, -1) 400 matches2 := regFullDataType.FindAllStringSubmatch(fullDataType, -1) 401 if (len(matches) == 1 && matches[0][1] != fmt.Sprint(field.Size) || !field.PrimaryKey) && 402 (len(matches2) == 1 && matches2[0][1] != fmt.Sprint(length) && ok) { 403 alterColumn = true 404 } 405 } 406 } 407 408 // check precision 409 if precision, _, ok := columnType.DecimalSize(); ok && int64(field.Precision) != precision { 410 if regexp.MustCompile(fmt.Sprintf("[^0-9]%d[^0-9]", field.Precision)).MatchString(m.DataTypeOf(field)) { 411 alterColumn = true 412 } 413 } 414 415 // check nullable 416 if nullable, ok := columnType.Nullable(); ok && nullable == field.NotNull { 417 // not primary key & database is nullable 418 if !field.PrimaryKey && nullable { 419 alterColumn = true 420 } 421 } 422 423 if alterColumn && !field.IgnoreMigration { 424 return m.DB.Migrator().AlterColumn(value, field.Name) 425 } 426 427 return nil 428 } 429 430 // ColumnTypes return columnTypes []gorm.ColumnType and execErr error 431 func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) { 432 columnTypes := make([]gorm.ColumnType, 0) 433 execErr := m.RunWithValue(value, func(stmt *gorm.Statement) (err error) { 434 rows, err := m.DB.Session(&gorm.Session{}).Table(stmt.Table).Limit(1).Rows() 435 if err != nil { 436 return err 437 } 438 439 defer func() { 440 err = rows.Close() 441 }() 442 443 var rawColumnTypes []*sql.ColumnType 444 rawColumnTypes, err = rows.ColumnTypes() 445 if err != nil { 446 return err 447 } 448 449 for _, c := range rawColumnTypes { 450 columnTypes = append(columnTypes, c) 451 } 452 453 return 454 }) 455 456 return columnTypes, execErr 457 } 458 459 func (m Migrator) CreateView(name string, option gorm.ViewOption) error { 460 return gorm.ErrNotImplemented 461 } 462 463 func (m Migrator) DropView(name string) error { 464 return gorm.ErrNotImplemented 465 } 466 467 func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) { 468 sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??" 469 if constraint.OnDelete != "" { 470 sql += " ON DELETE " + constraint.OnDelete 471 } 472 473 if constraint.OnUpdate != "" { 474 sql += " ON UPDATE " + constraint.OnUpdate 475 } 476 477 var foreignKeys, references []interface{} 478 for _, field := range constraint.ForeignKeys { 479 foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName}) 480 } 481 482 for _, field := range constraint.References { 483 references = append(references, clause.Column{Name: field.DBName}) 484 } 485 results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references) 486 return 487 } 488 489 func (m Migrator) GuessConstraintAndTable(stmt *gorm.Statement, name string) (_ *schema.Constraint, _ *schema.Check, table string) { 490 if stmt.Schema == nil { 491 return nil, nil, stmt.Table 492 } 493 494 checkConstraints := stmt.Schema.ParseCheckConstraints() 495 if chk, ok := checkConstraints[name]; ok { 496 return nil, &chk, stmt.Table 497 } 498 499 getTable := func(rel *schema.Relationship) string { 500 switch rel.Type { 501 case schema.HasOne, schema.HasMany: 502 return rel.FieldSchema.Table 503 case schema.Many2Many: 504 return rel.JoinTable.Table 505 } 506 return stmt.Table 507 } 508 509 for _, rel := range stmt.Schema.Relationships.Relations { 510 if constraint := rel.ParseConstraint(); constraint != nil && constraint.Name == name { 511 return constraint, nil, getTable(rel) 512 } 513 } 514 515 if field := stmt.Schema.LookUpField(name); field != nil { 516 for k := range checkConstraints { 517 if checkConstraints[k].Field == field { 518 v := checkConstraints[k] 519 return nil, &v, stmt.Table 520 } 521 } 522 523 for _, rel := range stmt.Schema.Relationships.Relations { 524 if constraint := rel.ParseConstraint(); constraint != nil && rel.Field == field { 525 return constraint, nil, getTable(rel) 526 } 527 } 528 } 529 530 return nil, nil, stmt.Schema.Table 531 } 532 533 func (m Migrator) CreateConstraint(value interface{}, name string) error { 534 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 535 constraint, chk, table := m.GuessConstraintAndTable(stmt, name) 536 if chk != nil { 537 return m.DB.Exec( 538 "ALTER TABLE ? ADD CONSTRAINT ? CHECK (?)", 539 m.CurrentTable(stmt), clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}, 540 ).Error 541 } 542 543 if constraint != nil { 544 var vars = []interface{}{clause.Table{Name: table}} 545 if stmt.TableExpr != nil { 546 vars[0] = stmt.TableExpr 547 } 548 sql, values := buildConstraint(constraint) 549 return m.DB.Exec("ALTER TABLE ? ADD "+sql, append(vars, values...)...).Error 550 } 551 552 return nil 553 }) 554 } 555 556 func (m Migrator) DropConstraint(value interface{}, name string) error { 557 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 558 constraint, chk, table := m.GuessConstraintAndTable(stmt, name) 559 if constraint != nil { 560 name = constraint.Name 561 } else if chk != nil { 562 name = chk.Name 563 } 564 return m.DB.Exec("ALTER TABLE ? DROP CONSTRAINT ?", clause.Table{Name: table}, clause.Column{Name: name}).Error 565 }) 566 } 567 568 func (m Migrator) HasConstraint(value interface{}, name string) bool { 569 var count int64 570 m.RunWithValue(value, func(stmt *gorm.Statement) error { 571 currentDatabase := m.DB.Migrator().CurrentDatabase() 572 constraint, chk, table := m.GuessConstraintAndTable(stmt, name) 573 if constraint != nil { 574 name = constraint.Name 575 } else if chk != nil { 576 name = chk.Name 577 } 578 579 return m.DB.Raw( 580 "SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = ? AND table_name = ? AND constraint_name = ?", 581 currentDatabase, table, name, 582 ).Row().Scan(&count) 583 }) 584 585 return count > 0 586 } 587 588 func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statement) (results []interface{}) { 589 for _, opt := range opts { 590 str := stmt.Quote(opt.DBName) 591 if opt.Expression != "" { 592 str = opt.Expression 593 } else if opt.Length > 0 { 594 str += fmt.Sprintf("(%d)", opt.Length) 595 } 596 597 if opt.Collate != "" { 598 str += " COLLATE " + opt.Collate 599 } 600 601 if opt.Sort != "" { 602 str += " " + opt.Sort 603 } 604 results = append(results, clause.Expr{SQL: str}) 605 } 606 return 607 } 608 609 type BuildIndexOptionsInterface interface { 610 BuildIndexOptions([]schema.IndexOption, *gorm.Statement) []interface{} 611 } 612 613 func (m Migrator) CreateIndex(value interface{}, name string) error { 614 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 615 if idx := stmt.Schema.LookIndex(name); idx != nil { 616 opts := m.DB.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt) 617 values := []interface{}{clause.Column{Name: idx.Name}, m.CurrentTable(stmt), opts} 618 619 createIndexSQL := "CREATE " 620 if idx.Class != "" { 621 createIndexSQL += idx.Class + " " 622 } 623 createIndexSQL += "INDEX ? ON ??" 624 625 if idx.Type != "" { 626 createIndexSQL += " USING " + idx.Type 627 } 628 629 if idx.Comment != "" { 630 createIndexSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment) 631 } 632 633 if idx.Option != "" { 634 createIndexSQL += " " + idx.Option 635 } 636 637 return m.DB.Exec(createIndexSQL, values...).Error 638 } 639 640 return fmt.Errorf("failed to create index with name %s", name) 641 }) 642 } 643 644 func (m Migrator) DropIndex(value interface{}, name string) error { 645 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 646 if idx := stmt.Schema.LookIndex(name); idx != nil { 647 name = idx.Name 648 } 649 650 return m.DB.Exec("DROP INDEX ? ON ?", clause.Column{Name: name}, m.CurrentTable(stmt)).Error 651 }) 652 } 653 654 func (m Migrator) HasIndex(value interface{}, name string) bool { 655 var count int64 656 m.RunWithValue(value, func(stmt *gorm.Statement) error { 657 currentDatabase := m.DB.Migrator().CurrentDatabase() 658 if idx := stmt.Schema.LookIndex(name); idx != nil { 659 name = idx.Name 660 } 661 662 return m.DB.Raw( 663 "SELECT count(*) FROM information_schema.statistics WHERE table_schema = ? AND table_name = ? AND index_name = ?", 664 currentDatabase, stmt.Table, name, 665 ).Row().Scan(&count) 666 }) 667 668 return count > 0 669 } 670 671 func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error { 672 return m.RunWithValue(value, func(stmt *gorm.Statement) error { 673 return m.DB.Exec( 674 "ALTER TABLE ? RENAME INDEX ? TO ?", 675 m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName}, 676 ).Error 677 }) 678 } 679 680 func (m Migrator) CurrentDatabase() (name string) { 681 m.DB.Raw("SELECT DATABASE()").Row().Scan(&name) 682 return 683 } 684 685 // ReorderModels reorder models according to constraint dependencies 686 func (m Migrator) ReorderModels(values []interface{}, autoAdd bool) (results []interface{}) { 687 type Dependency struct { 688 *gorm.Statement 689 Depends []*schema.Schema 690 } 691 692 var ( 693 modelNames, orderedModelNames []string 694 orderedModelNamesMap = map[string]bool{} 695 parsedSchemas = map[*schema.Schema]bool{} 696 valuesMap = map[string]Dependency{} 697 insertIntoOrderedList func(name string) 698 parseDependence func(value interface{}, addToList bool) 699 ) 700 701 parseDependence = func(value interface{}, addToList bool) { 702 dep := Dependency{ 703 Statement: &gorm.Statement{DB: m.DB, Dest: value}, 704 } 705 beDependedOn := map[*schema.Schema]bool{} 706 if err := dep.Parse(value); err != nil { 707 m.DB.Logger.Error(context.Background(), "failed to parse value %#v, got error %v", value, err) 708 } 709 if _, ok := parsedSchemas[dep.Statement.Schema]; ok { 710 return 711 } 712 parsedSchemas[dep.Statement.Schema] = true 713 714 for _, rel := range dep.Schema.Relationships.Relations { 715 if c := rel.ParseConstraint(); c != nil && c.Schema == dep.Statement.Schema && c.Schema != c.ReferenceSchema { 716 dep.Depends = append(dep.Depends, c.ReferenceSchema) 717 } 718 719 if rel.Type == schema.HasOne || rel.Type == schema.HasMany { 720 beDependedOn[rel.FieldSchema] = true 721 } 722 723 if rel.JoinTable != nil { 724 // append join value 725 defer func(rel *schema.Relationship, joinValue interface{}) { 726 if !beDependedOn[rel.FieldSchema] { 727 dep.Depends = append(dep.Depends, rel.FieldSchema) 728 } else { 729 fieldValue := reflect.New(rel.FieldSchema.ModelType).Interface() 730 parseDependence(fieldValue, autoAdd) 731 } 732 parseDependence(joinValue, autoAdd) 733 }(rel, reflect.New(rel.JoinTable.ModelType).Interface()) 734 } 735 } 736 737 valuesMap[dep.Schema.Table] = dep 738 739 if addToList { 740 modelNames = append(modelNames, dep.Schema.Table) 741 } 742 } 743 744 insertIntoOrderedList = func(name string) { 745 if _, ok := orderedModelNamesMap[name]; ok { 746 return // avoid loop 747 } 748 orderedModelNamesMap[name] = true 749 750 if autoAdd { 751 dep := valuesMap[name] 752 for _, d := range dep.Depends { 753 if _, ok := valuesMap[d.Table]; ok { 754 insertIntoOrderedList(d.Table) 755 } else { 756 parseDependence(reflect.New(d.ModelType).Interface(), autoAdd) 757 insertIntoOrderedList(d.Table) 758 } 759 } 760 } 761 762 orderedModelNames = append(orderedModelNames, name) 763 } 764 765 for _, value := range values { 766 if v, ok := value.(string); ok { 767 results = append(results, v) 768 } else { 769 parseDependence(value, true) 770 } 771 } 772 773 for _, name := range modelNames { 774 insertIntoOrderedList(name) 775 } 776 777 for _, name := range orderedModelNames { 778 results = append(results, valuesMap[name].Statement.Dest) 779 } 780 return 781 } 782 783 func (m Migrator) CurrentTable(stmt *gorm.Statement) interface{} { 784 if stmt.TableExpr != nil { 785 return *stmt.TableExpr 786 } 787 return clause.Table{Name: stmt.Table} 788 }