mirror of https://github.com/go-gorm/gorm.git
554 lines
16 KiB
Go
554 lines
16 KiB
Go
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 droped 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) {
|
|
fullSupported := map[string]bool{"sqlite": true, "mysql": true, "postgres": true, "sqlserver": true}[DB.Dialector.Name()]
|
|
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"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
if err := DB.Table("column_structs").Migrator().AlterColumn(&ColumnStruct2{}, "Name"); err != nil {
|
|
t.Fatalf("no error should happened when alter 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(); (fullSupported || 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(); ((fullSupported && !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(); (fullSupported || ok) && v != "18" {
|
|
t.Fatalf("column age default value should be correct, name: %v, column: %#v", columnType.Name(), columnType)
|
|
}
|
|
if v, ok := columnType.Comment(); ((fullSupported && !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(); (fullSupported || 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)
|
|
}
|
|
}
|
|
}
|