package tests_test import ( "math/rand" "reflect" "strings" "testing" "time" "gorm.io/gorm" "gorm.io/gorm/schema" . "gorm.io/gorm/utils/tests" ) func TestMigrate(t *testing.T) { allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}} rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(allModels), func(i, j int) { allModels[i], allModels[j] = allModels[j], allModels[i] }) DB.Migrator().DropTable("user_speaks", "user_friends", "ccc") if err := DB.Migrator().DropTable(allModels...); err != nil { t.Fatalf("Failed to drop table, got error %v", err) } if err := DB.AutoMigrate(allModels...); err != nil { t.Fatalf("Failed to auto migrate, but got error %v", err) } if tables, err := DB.Migrator().GetTables(); err != nil { t.Fatalf("Failed to get database all tables, but got error %v", err) } else { for _, t1 := range []string{"users", "accounts", "pets", "companies", "toys", "languages"} { hasTable := false for _, t2 := range tables { if t2 == t1 { hasTable = true break } } if !hasTable { t.Fatalf("Failed to get table %v when GetTables", t1) } } } for _, m := range allModels { if !DB.Migrator().HasTable(m) { t.Fatalf("Failed to create table for %#v", m) } } DB.Scopes(func(db *gorm.DB) *gorm.DB { return db.Table("ccc") }).Migrator().CreateTable(&Company{}) if !DB.Migrator().HasTable("ccc") { t.Errorf("failed to create table ccc") } for _, indexes := range [][2]string{ {"user_speaks", "fk_user_speaks_user"}, {"user_speaks", "fk_user_speaks_language"}, {"user_friends", "fk_user_friends_user"}, {"user_friends", "fk_user_friends_friends"}, {"accounts", "fk_users_account"}, {"users", "fk_users_team"}, {"users", "fk_users_company"}, } { if !DB.Migrator().HasConstraint(indexes[0], indexes[1]) { t.Fatalf("Failed to find index for many2many for %v %v", indexes[0], indexes[1]) } } } func TestAutoMigrateSelfReferential(t *testing.T) { type MigratePerson struct { ID uint Name string ManagerID *uint Manager *MigratePerson } DB.Migrator().DropTable(&MigratePerson{}) if err := DB.AutoMigrate(&MigratePerson{}); err != nil { t.Fatalf("Failed to auto migrate, but got error %v", err) } if !DB.Migrator().HasConstraint("migrate_people", "fk_migrate_people_manager") { t.Fatalf("Failed to find has one constraint between people and managers") } } func TestSmartMigrateColumn(t *testing.T) { fullSupported := map[string]bool{"mysql": true, "postgres": true}[DB.Dialector.Name()] type UserMigrateColumn struct { ID uint Name string Salary float64 Birthday time.Time `gorm:"precision:4"` } DB.Migrator().DropTable(&UserMigrateColumn{}) DB.AutoMigrate(&UserMigrateColumn{}) type UserMigrateColumn2 struct { ID uint Name string `gorm:"size:128"` Salary float64 `gorm:"precision:2"` Birthday time.Time `gorm:"precision:2"` NameIgnoreMigration string `gorm:"size:100"` } if err := DB.Table("user_migrate_columns").AutoMigrate(&UserMigrateColumn2{}); err != nil { t.Fatalf("failed to auto migrate, got error: %v", err) } columnTypes, err := DB.Table("user_migrate_columns").Migrator().ColumnTypes(&UserMigrateColumn{}) if err != nil { t.Fatalf("failed to get column types, got error: %v", err) } for _, columnType := range columnTypes { switch columnType.Name() { case "name": if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 128 { t.Fatalf("name's length should be 128, but got %v", length) } case "salary": if precision, o, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 2 { t.Fatalf("salary's precision should be 2, but got %v %v", precision, o) } case "birthday": if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 2 { t.Fatalf("birthday's precision should be 2, but got %v", precision) } } } type UserMigrateColumn3 struct { ID uint Name string `gorm:"size:256"` Salary float64 `gorm:"precision:3"` Birthday time.Time `gorm:"precision:3"` NameIgnoreMigration string `gorm:"size:128;-:migration"` } if err := DB.Table("user_migrate_columns").AutoMigrate(&UserMigrateColumn3{}); err != nil { t.Fatalf("failed to auto migrate, got error: %v", err) } columnTypes, err = DB.Table("user_migrate_columns").Migrator().ColumnTypes(&UserMigrateColumn{}) if err != nil { t.Fatalf("failed to get column types, got error: %v", err) } for _, columnType := range columnTypes { switch columnType.Name() { case "name": if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 256 { t.Fatalf("name's length should be 128, but got %v", length) } case "salary": if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 3 { t.Fatalf("salary's precision should be 2, but got %v", precision) } case "birthday": if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 3 { t.Fatalf("birthday's precision should be 2, but got %v", precision) } case "name_ignore_migration": if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 100 { t.Fatalf("name_ignore_migration's length should still be 100 but got %v", length) } } } } func TestMigrateWithColumnComment(t *testing.T) { type UserWithColumnComment struct { gorm.Model Name string `gorm:"size:111;comment:this is a 字段"` } if err := DB.Migrator().DropTable(&UserWithColumnComment{}); err != nil { t.Fatalf("Failed to drop table, got error %v", err) } if err := DB.AutoMigrate(&UserWithColumnComment{}); err != nil { t.Fatalf("Failed to auto migrate, but got error %v", err) } } func TestMigrateWithIndexComment(t *testing.T) { if DB.Dialector.Name() != "mysql" { t.Skip() } type UserWithIndexComment struct { gorm.Model Name string `gorm:"size:111;index:,comment:这是一个index"` } if err := DB.Migrator().DropTable(&UserWithIndexComment{}); err != nil { t.Fatalf("Failed to drop table, got error %v", err) } if err := DB.AutoMigrate(&UserWithIndexComment{}); err != nil { t.Fatalf("Failed to auto migrate, but got error %v", err) } } func TestMigrateWithUniqueIndex(t *testing.T) { type UserWithUniqueIndex struct { ID int Name string `gorm:"size:20;index:idx_name,unique"` Date time.Time `gorm:"index:idx_name,unique"` } DB.Migrator().DropTable(&UserWithUniqueIndex{}) if err := DB.AutoMigrate(&UserWithUniqueIndex{}); err != nil { t.Fatalf("failed to migrate, got %v", err) } if !DB.Migrator().HasIndex(&UserWithUniqueIndex{}, "idx_name") { t.Errorf("Failed to find created index") } } func TestMigrateTable(t *testing.T) { type TableStruct struct { gorm.Model Name string } DB.Migrator().DropTable(&TableStruct{}) DB.AutoMigrate(&TableStruct{}) if !DB.Migrator().HasTable(&TableStruct{}) { t.Fatalf("should found created table") } type NewTableStruct struct { gorm.Model Name string } if err := DB.Migrator().RenameTable(&TableStruct{}, &NewTableStruct{}); err != nil { t.Fatalf("Failed to rename table, got error %v", err) } if !DB.Migrator().HasTable("new_table_structs") { t.Fatal("should found renamed table") } DB.Migrator().DropTable("new_table_structs") if DB.Migrator().HasTable(&NewTableStruct{}) { t.Fatal("should not found dropped table") } } func TestMigrateIndexes(t *testing.T) { type IndexStruct struct { gorm.Model Name string `gorm:"size:255;index"` } DB.Migrator().DropTable(&IndexStruct{}) DB.AutoMigrate(&IndexStruct{}) if err := DB.Migrator().DropIndex(&IndexStruct{}, "Name"); err != nil { t.Fatalf("Failed to drop index for user's name, got err %v", err) } if err := DB.Migrator().CreateIndex(&IndexStruct{}, "Name"); err != nil { t.Fatalf("Got error when tried to create index: %+v", err) } if !DB.Migrator().HasIndex(&IndexStruct{}, "Name") { t.Fatalf("Failed to find index for user's name") } if err := DB.Migrator().DropIndex(&IndexStruct{}, "Name"); err != nil { t.Fatalf("Failed to drop index for user's name, got err %v", err) } if DB.Migrator().HasIndex(&IndexStruct{}, "Name") { t.Fatalf("Should not find index for user's name after delete") } if err := DB.Migrator().CreateIndex(&IndexStruct{}, "Name"); err != nil { t.Fatalf("Got error when tried to create index: %+v", err) } if err := DB.Migrator().RenameIndex(&IndexStruct{}, "idx_index_structs_name", "idx_users_name_1"); err != nil { t.Fatalf("no error should happen when rename index, but got %v", err) } if !DB.Migrator().HasIndex(&IndexStruct{}, "idx_users_name_1") { t.Fatalf("Should find index for user's name after rename") } if err := DB.Migrator().DropIndex(&IndexStruct{}, "idx_users_name_1"); err != nil { t.Fatalf("Failed to drop index for user's name, got err %v", err) } if DB.Migrator().HasIndex(&IndexStruct{}, "idx_users_name_1") { t.Fatalf("Should not find index for user's name after delete") } } func TestMigrateColumns(t *testing.T) { sqlite := DB.Dialector.Name() == "sqlite" sqlserver := DB.Dialector.Name() == "sqlserver" type ColumnStruct struct { gorm.Model Name string Age int `gorm:"default:18;comment:my age"` Code string `gorm:"unique;comment:my code;"` Code2 string Code3 string `gorm:"unique"` } DB.Migrator().DropTable(&ColumnStruct{}) if err := DB.AutoMigrate(&ColumnStruct{}); err != nil { t.Errorf("Failed to migrate, got %v", err) } type ColumnStruct2 struct { gorm.Model Name string `gorm:"size:100"` Code string `gorm:"unique;comment:my code2;default:hello"` Code2 string `gorm:"unique"` // Code3 string } if err := DB.Table("column_structs").Migrator().AlterColumn(&ColumnStruct{}, "Name"); err != nil { t.Fatalf("no error should happened when alter column, but got %v", err) } if err := DB.Table("column_structs").AutoMigrate(&ColumnStruct2{}); err != nil { t.Fatalf("no error should happened when auto migrate column, but got %v", err) } if columnTypes, err := DB.Migrator().ColumnTypes(&ColumnStruct{}); err != nil { t.Fatalf("no error should returns for ColumnTypes") } else { stmt := &gorm.Statement{DB: DB} stmt.Parse(&ColumnStruct2{}) for _, columnType := range columnTypes { switch columnType.Name() { case "id": if v, ok := columnType.PrimaryKey(); !ok || !v { t.Fatalf("column id primary key should be correct, name: %v, column: %#v", columnType.Name(), columnType) } case "name": dataType := DB.Dialector.DataTypeOf(stmt.Schema.LookUpField(columnType.Name())) if !strings.Contains(strings.ToUpper(dataType), strings.ToUpper(columnType.DatabaseTypeName())) { t.Fatalf("column name type should be correct, name: %v, length: %v, expects: %v, column: %#v", columnType.Name(), columnType.DatabaseTypeName(), dataType, columnType) } if length, ok := columnType.Length(); !sqlite && (!ok || length != 100) { t.Fatalf("column name length should be correct, name: %v, length: %v, expects: %v, column: %#v", columnType.Name(), length, 100, columnType) } case "age": if v, ok := columnType.DefaultValue(); !ok || v != "18" { t.Fatalf("column age default value should be correct, name: %v, column: %#v", columnType.Name(), columnType) } if v, ok := columnType.Comment(); !sqlite && !sqlserver && (!ok || v != "my age") { t.Fatalf("column age comment should be correct, name: %v, column: %#v", columnType.Name(), columnType) } case "code": if v, ok := columnType.Unique(); !ok || !v { t.Fatalf("column code unique should be correct, name: %v, column: %#v", columnType.Name(), columnType) } if v, ok := columnType.DefaultValue(); !sqlserver && (!ok || v != "hello") { t.Fatalf("column code default value should be correct, name: %v, column: %#v", columnType.Name(), columnType) } if v, ok := columnType.Comment(); !sqlite && !sqlserver && (!ok || v != "my code2") { t.Fatalf("column code comment should be correct, name: %v, column: %#v", columnType.Name(), columnType) } case "code2": if v, ok := columnType.Unique(); !sqlserver && (!ok || !v) { t.Fatalf("column code2 unique should be correct, name: %v, column: %#v", columnType.Name(), columnType) } case "code3": // TODO // if v, ok := columnType.Unique(); !ok || v { // t.Fatalf("column code unique should be correct, name: %v, column: %#v", columnType.Name(), columnType) // } } } } type NewColumnStruct struct { gorm.Model Name string NewName string } if err := DB.Table("column_structs").Migrator().AddColumn(&NewColumnStruct{}, "NewName"); err != nil { t.Fatalf("Failed to add column, got %v", err) } if !DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "NewName") { t.Fatalf("Failed to find added column") } if err := DB.Table("column_structs").Migrator().DropColumn(&NewColumnStruct{}, "NewName"); err != nil { t.Fatalf("Failed to add column, got %v", err) } if DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "NewName") { t.Fatalf("Found deleted column") } if err := DB.Table("column_structs").Migrator().AddColumn(&NewColumnStruct{}, "NewName"); err != nil { t.Fatalf("Failed to add column, got %v", err) } if err := DB.Table("column_structs").Migrator().RenameColumn(&NewColumnStruct{}, "NewName", "new_new_name"); err != nil { t.Fatalf("Failed to add column, got %v", err) } if !DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "new_new_name") { t.Fatalf("Failed to found renamed column") } if err := DB.Table("column_structs").Migrator().DropColumn(&NewColumnStruct{}, "new_new_name"); err != nil { t.Fatalf("Failed to add column, got %v", err) } if DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "new_new_name") { t.Fatalf("Found deleted column") } } func TestMigrateConstraint(t *testing.T) { names := []string{"Account", "fk_users_account", "Pets", "fk_users_pets", "Company", "fk_users_company", "Team", "fk_users_team", "Languages", "fk_users_languages"} for _, name := range names { if !DB.Migrator().HasConstraint(&User{}, name) { DB.Migrator().CreateConstraint(&User{}, name) } if err := DB.Migrator().DropConstraint(&User{}, name); err != nil { t.Fatalf("failed to drop constraint %v, got error %v", name, err) } if DB.Migrator().HasConstraint(&User{}, name) { t.Fatalf("constraint %v should been deleted", name) } if err := DB.Migrator().CreateConstraint(&User{}, name); err != nil { t.Fatalf("failed to create constraint %v, got error %v", name, err) } if !DB.Migrator().HasConstraint(&User{}, name) { t.Fatalf("failed to found constraint %v", name) } } } type DynamicUser struct { gorm.Model Name string CompanyID string `gorm:"index"` } // To test auto migrate crate indexes for dynamic table name // https://github.com/go-gorm/gorm/issues/4752 func TestMigrateIndexesWithDynamicTableName(t *testing.T) { // Create primary table if err := DB.AutoMigrate(&DynamicUser{}); err != nil { t.Fatalf("AutoMigrate create table error: %#v", err) } // Create sub tables for _, v := range []string{"01", "02", "03"} { tableName := "dynamic_users_" + v m := DB.Scopes(func(db *gorm.DB) *gorm.DB { return db.Table(tableName) }).Migrator() if err := m.AutoMigrate(&DynamicUser{}); err != nil { t.Fatalf("AutoMigrate create table error: %#v", err) } if !m.HasTable(tableName) { t.Fatalf("AutoMigrate expected %#v exist, but not.", tableName) } if !m.HasIndex(&DynamicUser{}, "CompanyID") { t.Fatalf("Should have index on %s", "CompanyI.") } if !m.HasIndex(&DynamicUser{}, "DeletedAt") { t.Fatalf("Should have index on deleted_at.") } } } // check column order after migration, flaky test // https://github.com/go-gorm/gorm/issues/4351 func TestMigrateColumnOrder(t *testing.T) { type UserMigrateColumn struct { ID uint } DB.Migrator().DropTable(&UserMigrateColumn{}) DB.AutoMigrate(&UserMigrateColumn{}) type UserMigrateColumn2 struct { ID uint F1 string F2 string F3 string F4 string F5 string F6 string F7 string F8 string F9 string F10 string F11 string F12 string F13 string F14 string F15 string F16 string F17 string F18 string F19 string F20 string F21 string F22 string F23 string F24 string F25 string F26 string F27 string F28 string F29 string F30 string F31 string F32 string F33 string F34 string F35 string } if err := DB.Table("user_migrate_columns").AutoMigrate(&UserMigrateColumn2{}); err != nil { t.Fatalf("failed to auto migrate, got error: %v", err) } columnTypes, err := DB.Table("user_migrate_columns").Migrator().ColumnTypes(&UserMigrateColumn2{}) if err != nil { t.Fatalf("failed to get column types, got error: %v", err) } typ := reflect.Indirect(reflect.ValueOf(&UserMigrateColumn2{})).Type() numField := typ.NumField() if numField != len(columnTypes) { t.Fatalf("column's number not match struct and ddl, %d != %d", numField, len(columnTypes)) } namer := schema.NamingStrategy{} for i := 0; i < numField; i++ { expectName := namer.ColumnName("", typ.Field(i).Name) if columnTypes[i].Name() != expectName { t.Fatalf("column order not match struct and ddl, idx %d: %s != %s", i, columnTypes[i].Name(), expectName) } } } // https://github.com/go-gorm/gorm/issues/5047 func TestMigrateSerialColumn(t *testing.T) { if DB.Dialector.Name() != "postgres" { return } type Event struct { ID uint `gorm:"primarykey"` UID uint32 } type Event1 struct { ID uint `gorm:"primarykey"` UID uint32 `gorm:"not null;autoIncrement"` } type Event2 struct { ID uint `gorm:"primarykey"` UID uint16 `gorm:"not null;autoIncrement"` } var err error err = DB.Migrator().DropTable(&Event{}) if err != nil { t.Errorf("DropTable err:%v", err) } // create sequence err = DB.Table("events").AutoMigrate(&Event1{}) if err != nil { t.Errorf("AutoMigrate err:%v", err) } // delete sequence err = DB.Table("events").AutoMigrate(&Event{}) if err != nil { t.Errorf("AutoMigrate err:%v", err) } // update sequence err = DB.Table("events").AutoMigrate(&Event1{}) if err != nil { t.Errorf("AutoMigrate err:%v", err) } err = DB.Table("events").AutoMigrate(&Event2{}) if err != nil { t.Errorf("AutoMigrate err:%v", err) } DB.Table("events").Save(&Event2{}) DB.Table("events").Save(&Event2{}) DB.Table("events").Save(&Event2{}) events := make([]*Event, 0) DB.Table("events").Find(&events) AssertEqual(t, 3, len(events)) for _, v := range events { AssertEqual(t, v.ID, v.UID) } } // https://github.com/go-gorm/gorm/issues/5300 func TestMigrateWithSpecialName(t *testing.T) { DB.AutoMigrate(&Coupon{}) DB.Table("coupon_product_1").AutoMigrate(&CouponProduct{}) DB.Table("coupon_product_2").AutoMigrate(&CouponProduct{}) AssertEqual(t, true, DB.Migrator().HasTable("coupons")) AssertEqual(t, true, DB.Migrator().HasTable("coupon_product_1")) AssertEqual(t, true, DB.Migrator().HasTable("coupon_product_2")) }