mirror of https://github.com/go-gorm/gorm.git
1938 lines
56 KiB
Go
1938 lines
56 KiB
Go
package tests_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"gorm.io/driver/postgres"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"gorm.io/gorm/logger"
|
|
"gorm.io/gorm/migrator"
|
|
"gorm.io/gorm/schema"
|
|
"gorm.io/gorm/utils"
|
|
. "gorm.io/gorm/utils/tests"
|
|
)
|
|
|
|
func TestMigrate(t *testing.T) {
|
|
allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}, &Tools{}}
|
|
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, 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", "tools"} {
|
|
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 TestAutoMigrateInt8PG(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
type Smallint int8
|
|
|
|
type MigrateInt struct {
|
|
Int8 Smallint
|
|
}
|
|
|
|
tracer := Tracer{
|
|
Logger: DB.Config.Logger,
|
|
Test: func(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
|
sql, _ := fc()
|
|
if strings.HasPrefix(sql, "ALTER TABLE \"migrate_ints\" ALTER COLUMN \"int8\" TYPE smallint") {
|
|
t.Fatalf("shouldn't execute ALTER COLUMN TYPE if such type is already existed in DB schema: sql: %s",
|
|
sql)
|
|
}
|
|
},
|
|
}
|
|
|
|
DB.Migrator().DropTable(&MigrateInt{})
|
|
|
|
// The first AutoMigrate to make table with field with correct type
|
|
if err := DB.AutoMigrate(&MigrateInt{}); err != nil {
|
|
t.Fatalf("Failed to auto migrate: error: %v", err)
|
|
}
|
|
|
|
// make new session to set custom logger tracer
|
|
session := DB.Session(&gorm.Session{Logger: tracer})
|
|
|
|
// The second AutoMigrate to catch an error
|
|
if err := session.AutoMigrate(&MigrateInt{}); err != nil {
|
|
t.Fatalf("Failed to auto migrate: error: %v", err)
|
|
}
|
|
}
|
|
|
|
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"`
|
|
UName string `gorm:"uniqueIndex;size:255"`
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
if !DB.Migrator().HasIndex(&UserWithUniqueIndex{}, "idx_user_with_unique_indices_u_name") {
|
|
t.Errorf("Failed to find created index")
|
|
}
|
|
|
|
if err := DB.AutoMigrate(&UserWithUniqueIndex{}); err != nil {
|
|
t.Fatalf("failed to migrate, got %v", err)
|
|
}
|
|
|
|
if !DB.Migrator().HasIndex(&UserWithUniqueIndex{}, "idx_user_with_unique_indices_u_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 TestMigrateWithQuotedIndex(t *testing.T) {
|
|
if DB.Dialector.Name() != "mysql" {
|
|
t.Skip()
|
|
}
|
|
|
|
type QuotedIndexStruct struct {
|
|
gorm.Model
|
|
Name string `gorm:"size:255;index:AS"` // AS is one of MySQL reserved words
|
|
}
|
|
|
|
if err := DB.Migrator().DropTable(&QuotedIndexStruct{}); err != nil {
|
|
t.Fatalf("Failed to drop table, got error %v", err)
|
|
}
|
|
|
|
if err := DB.AutoMigrate(&QuotedIndexStruct{}); err != nil {
|
|
t.Fatalf("Failed to auto migrate, but got error %v", err)
|
|
}
|
|
}
|
|
|
|
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 TestTiDBMigrateColumns(t *testing.T) {
|
|
if !isTiDB() {
|
|
t.Skip()
|
|
}
|
|
|
|
// TiDB can't change column constraint and has auto_random feature
|
|
type ColumnStruct struct {
|
|
ID int `gorm:"primarykey;default:auto_random()"`
|
|
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 {
|
|
ID int `gorm:"primarykey;default:auto_random()"`
|
|
Name string `gorm:"size:100"`
|
|
Code string `gorm:"unique;comment:my code2;default:hello"`
|
|
Code2 string `gorm:"comment:my code2;default:hello"`
|
|
}
|
|
|
|
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(); !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(); !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(); !ok || v != "hello" {
|
|
t.Fatalf("column code default value should be correct, name: %v, column: %#v, default value: %v",
|
|
columnType.Name(), columnType, v)
|
|
}
|
|
if v, ok := columnType.Comment(); !ok || v != "my code2" {
|
|
t.Fatalf("column code comment should be correct, name: %v, column: %#v", columnType.Name(),
|
|
columnType)
|
|
}
|
|
case "code2":
|
|
// Code2 string `gorm:"comment:my code2;default:hello"`
|
|
if v, ok := columnType.DefaultValue(); !ok || v != "hello" {
|
|
t.Fatalf("column code default value should be correct, name: %v, column: %#v, default value: %v",
|
|
columnType.Name(), columnType, v)
|
|
}
|
|
if v, ok := columnType.Comment(); !ok || v != "my code2" {
|
|
t.Fatalf("column code comment 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 TestMigrateColumns(t *testing.T) {
|
|
tidbSkip(t, "use another test case")
|
|
|
|
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, default value: %v",
|
|
columnType.Name(), columnType, v)
|
|
}
|
|
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) {
|
|
var err error
|
|
err = DB.AutoMigrate(&Coupon{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
err = DB.Table("coupon_product_1").AutoMigrate(&CouponProduct{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
err = DB.Table("coupon_product_2").AutoMigrate(&CouponProduct{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
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"))
|
|
}
|
|
|
|
// https://github.com/go-gorm/gorm/issues/4760
|
|
func TestMigrateAutoIncrement(t *testing.T) {
|
|
type AutoIncrementStruct struct {
|
|
ID int64 `gorm:"primarykey;autoIncrement"`
|
|
Field1 uint32 `gorm:"column:field1"`
|
|
Field2 float32 `gorm:"column:field2"`
|
|
}
|
|
|
|
if err := DB.AutoMigrate(&AutoIncrementStruct{}); err != nil {
|
|
t.Fatalf("AutoMigrate err: %v", err)
|
|
}
|
|
|
|
const ROWS = 10
|
|
for idx := 0; idx < ROWS; idx++ {
|
|
if err := DB.Create(&AutoIncrementStruct{}).Error; err != nil {
|
|
t.Fatalf("create auto_increment_struct fail, err: %v", err)
|
|
}
|
|
}
|
|
|
|
rows := make([]*AutoIncrementStruct, 0, ROWS)
|
|
if err := DB.Order("id ASC").Find(&rows).Error; err != nil {
|
|
t.Fatalf("find auto_increment_struct fail, err: %v", err)
|
|
}
|
|
|
|
ids := make([]int64, 0, len(rows))
|
|
for _, row := range rows {
|
|
ids = append(ids, row.ID)
|
|
}
|
|
lastID := ids[len(ids)-1]
|
|
|
|
if err := DB.Where("id IN (?)", ids).Delete(&AutoIncrementStruct{}).Error; err != nil {
|
|
t.Fatalf("delete auto_increment_struct fail, err: %v", err)
|
|
}
|
|
|
|
newRow := &AutoIncrementStruct{}
|
|
if err := DB.Create(newRow).Error; err != nil {
|
|
t.Fatalf("create auto_increment_struct fail, err: %v", err)
|
|
}
|
|
|
|
AssertEqual(t, newRow.ID, lastID+1)
|
|
}
|
|
|
|
// https://github.com/go-gorm/gorm/issues/5320
|
|
func TestPrimarykeyID(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
type MissPKLanguage struct {
|
|
ID string `gorm:"type:uuid;default:uuid_generate_v4()"`
|
|
Name string
|
|
}
|
|
|
|
type MissPKUser struct {
|
|
ID string `gorm:"type:uuid;default:uuid_generate_v4()"`
|
|
MissPKLanguages []MissPKLanguage `gorm:"many2many:miss_pk_user_languages;"`
|
|
}
|
|
|
|
var err error
|
|
err = DB.Migrator().DropTable(&MissPKUser{}, &MissPKLanguage{})
|
|
if err != nil {
|
|
t.Fatalf("DropTable err:%v", err)
|
|
}
|
|
|
|
DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`)
|
|
|
|
err = DB.AutoMigrate(&MissPKUser{}, &MissPKLanguage{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
// patch
|
|
err = DB.AutoMigrate(&MissPKUser{}, &MissPKLanguage{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentTimestamp(t *testing.T) {
|
|
if DB.Dialector.Name() != "mysql" {
|
|
return
|
|
}
|
|
type CurrentTimestampTest struct {
|
|
ID string `gorm:"primary_key"`
|
|
TimeAt *time.Time `gorm:"type:datetime;not null;default:CURRENT_TIMESTAMP;unique"`
|
|
}
|
|
var err error
|
|
err = DB.Migrator().DropTable(&CurrentTimestampTest{})
|
|
if err != nil {
|
|
t.Errorf("DropTable err:%v", err)
|
|
}
|
|
err = DB.AutoMigrate(&CurrentTimestampTest{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = DB.AutoMigrate(&CurrentTimestampTest{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
AssertEqual(t, true, DB.Migrator().HasConstraint(&CurrentTimestampTest{}, "uni_current_timestamp_tests_time_at"))
|
|
AssertEqual(t, false, DB.Migrator().HasIndex(&CurrentTimestampTest{}, "time_at"))
|
|
AssertEqual(t, false, DB.Migrator().HasIndex(&CurrentTimestampTest{}, "time_at_2"))
|
|
}
|
|
|
|
func TestUniqueColumn(t *testing.T) {
|
|
if DB.Dialector.Name() != "mysql" {
|
|
return
|
|
}
|
|
|
|
type UniqueTest struct {
|
|
ID string `gorm:"primary_key"`
|
|
Name string `gorm:"unique"`
|
|
}
|
|
|
|
type UniqueTest2 struct {
|
|
ID string `gorm:"primary_key"`
|
|
Name string `gorm:"unique;default:NULL"`
|
|
}
|
|
|
|
type UniqueTest3 struct {
|
|
ID string `gorm:"primary_key"`
|
|
Name string `gorm:"unique;default:''"`
|
|
}
|
|
|
|
type UniqueTest4 struct {
|
|
ID string `gorm:"primary_key"`
|
|
Name string `gorm:"unique;default:'123'"`
|
|
}
|
|
|
|
var err error
|
|
err = DB.Migrator().DropTable(&UniqueTest{})
|
|
if err != nil {
|
|
t.Errorf("DropTable err:%v", err)
|
|
}
|
|
|
|
err = DB.AutoMigrate(&UniqueTest{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
// null -> null
|
|
err = DB.AutoMigrate(&UniqueTest{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err := findColumnType(&UniqueTest{}, "name")
|
|
if err != nil {
|
|
t.Fatalf("findColumnType err:%v", err)
|
|
}
|
|
|
|
value, ok := ct.DefaultValue()
|
|
AssertEqual(t, "", value)
|
|
AssertEqual(t, false, ok)
|
|
|
|
// null -> null
|
|
err = DB.Table("unique_tests").AutoMigrate(&UniqueTest2{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
// not trigger alert column
|
|
AssertEqual(t, true, DB.Migrator().HasConstraint(&UniqueTest{}, "uni_unique_tests_name"))
|
|
AssertEqual(t, false, DB.Migrator().HasIndex(&UniqueTest{}, "name"))
|
|
AssertEqual(t, false, DB.Migrator().HasIndex(&UniqueTest{}, "name_1"))
|
|
AssertEqual(t, false, DB.Migrator().HasIndex(&UniqueTest{}, "name_2"))
|
|
|
|
ct, err = findColumnType(&UniqueTest{}, "name")
|
|
if err != nil {
|
|
t.Fatalf("findColumnType err:%v", err)
|
|
}
|
|
|
|
value, ok = ct.DefaultValue()
|
|
AssertEqual(t, "", value)
|
|
AssertEqual(t, false, ok)
|
|
|
|
tidbSkip(t, "can't change column constraint")
|
|
|
|
// null -> empty string
|
|
err = DB.Table("unique_tests").AutoMigrate(&UniqueTest3{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err = findColumnType(&UniqueTest{}, "name")
|
|
if err != nil {
|
|
t.Fatalf("findColumnType err:%v", err)
|
|
}
|
|
|
|
value, ok = ct.DefaultValue()
|
|
AssertEqual(t, "", value)
|
|
AssertEqual(t, true, ok)
|
|
|
|
// empty string -> 123
|
|
err = DB.Table("unique_tests").AutoMigrate(&UniqueTest4{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err = findColumnType(&UniqueTest{}, "name")
|
|
if err != nil {
|
|
t.Fatalf("findColumnType err:%v", err)
|
|
}
|
|
|
|
value, ok = ct.DefaultValue()
|
|
AssertEqual(t, "123", value)
|
|
AssertEqual(t, true, ok)
|
|
|
|
// 123 -> null
|
|
err = DB.Table("unique_tests").AutoMigrate(&UniqueTest2{})
|
|
if err != nil {
|
|
t.Fatalf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err = findColumnType(&UniqueTest{}, "name")
|
|
if err != nil {
|
|
t.Fatalf("findColumnType err:%v", err)
|
|
}
|
|
|
|
value, ok = ct.DefaultValue()
|
|
AssertEqual(t, "", value)
|
|
AssertEqual(t, false, ok)
|
|
}
|
|
|
|
func findColumnType(dest interface{}, columnName string) (
|
|
foundColumn gorm.ColumnType, err error,
|
|
) {
|
|
columnTypes, err := DB.Migrator().ColumnTypes(dest)
|
|
if err != nil {
|
|
err = fmt.Errorf("ColumnTypes err:%v", err)
|
|
return
|
|
}
|
|
|
|
for _, c := range columnTypes {
|
|
if c.Name() == columnName {
|
|
foundColumn = c
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func TestInvalidCachedPlanSimpleProtocol(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{})
|
|
if err != nil {
|
|
t.Errorf("Open err:%v", err)
|
|
}
|
|
|
|
type Object1 struct{}
|
|
type Object2 struct {
|
|
Field1 string
|
|
}
|
|
type Object3 struct {
|
|
Field2 string
|
|
}
|
|
db.Migrator().DropTable("objects")
|
|
|
|
err = db.Table("objects").AutoMigrate(&Object1{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").AutoMigrate(&Object2{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").AutoMigrate(&Object3{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
}
|
|
|
|
func TestInvalidCachedPlanPrepareStmt(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{PrepareStmt: true})
|
|
if err != nil {
|
|
t.Errorf("Open err:%v", err)
|
|
}
|
|
if debug := os.Getenv("DEBUG"); debug == "true" {
|
|
db.Logger = db.Logger.LogMode(logger.Info)
|
|
} else if debug == "false" {
|
|
db.Logger = db.Logger.LogMode(logger.Silent)
|
|
}
|
|
|
|
type Object1 struct {
|
|
ID uint
|
|
}
|
|
type Object2 struct {
|
|
ID uint
|
|
Field1 int `gorm:"type:int8"`
|
|
}
|
|
type Object3 struct {
|
|
ID uint
|
|
Field1 int `gorm:"type:int4"`
|
|
}
|
|
type Object4 struct {
|
|
ID uint
|
|
Field2 int
|
|
}
|
|
db.Migrator().DropTable("objects")
|
|
|
|
err = db.Table("objects").AutoMigrate(&Object1{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
err = db.Table("objects").Create(&Object1{}).Error
|
|
if err != nil {
|
|
t.Errorf("create err:%v", err)
|
|
}
|
|
|
|
// AddColumn
|
|
err = db.Table("objects").AutoMigrate(&Object2{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").Take(&Object2{}).Error
|
|
if err != nil {
|
|
t.Errorf("take err:%v", err)
|
|
}
|
|
|
|
// AlterColumn
|
|
err = db.Table("objects").AutoMigrate(&Object3{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").Take(&Object3{}).Error
|
|
if err != nil {
|
|
t.Errorf("take err:%v", err)
|
|
}
|
|
|
|
// AddColumn
|
|
err = db.Table("objects").AutoMigrate(&Object4{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").Take(&Object4{}).Error
|
|
if err != nil {
|
|
t.Errorf("take err:%v", err)
|
|
}
|
|
|
|
db.Table("objects").Migrator().RenameColumn(&Object4{}, "field2", "field3")
|
|
if err != nil {
|
|
t.Errorf("RenameColumn err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").Take(&Object4{}).Error
|
|
if err != nil {
|
|
t.Errorf("take err:%v", err)
|
|
}
|
|
|
|
db.Table("objects").Migrator().DropColumn(&Object4{}, "field3")
|
|
if err != nil {
|
|
t.Errorf("RenameColumn err:%v", err)
|
|
}
|
|
|
|
err = db.Table("objects").Take(&Object4{}).Error
|
|
if err != nil {
|
|
t.Errorf("take err:%v", err)
|
|
}
|
|
}
|
|
|
|
func TestDifferentTypeWithoutDeclaredLength(t *testing.T) {
|
|
type DiffType struct {
|
|
ID uint
|
|
Name string `gorm:"type:varchar(20)"`
|
|
}
|
|
|
|
type DiffType1 struct {
|
|
ID uint
|
|
Name string `gorm:"type:text"`
|
|
}
|
|
|
|
var err error
|
|
DB.Migrator().DropTable(&DiffType{})
|
|
|
|
err = DB.AutoMigrate(&DiffType{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err := findColumnType(&DiffType{}, "name")
|
|
if err != nil {
|
|
t.Errorf("findColumnType err:%v", err)
|
|
}
|
|
|
|
AssertEqual(t, "varchar", strings.ToLower(ct.DatabaseTypeName()))
|
|
|
|
err = DB.Table("diff_types").AutoMigrate(&DiffType1{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
ct, err = findColumnType(&DiffType{}, "name")
|
|
if err != nil {
|
|
t.Errorf("findColumnType err:%v", err)
|
|
}
|
|
|
|
AssertEqual(t, "text", strings.ToLower(ct.DatabaseTypeName()))
|
|
}
|
|
|
|
func TestMigrateArrayTypeModel(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
type ArrayTypeModel struct {
|
|
ID uint
|
|
Number string `gorm:"type:varchar(51);NOT NULL"`
|
|
TextArray []string `gorm:"type:text[];NOT NULL"`
|
|
NestedTextArray [][]string `gorm:"type:text[][]"`
|
|
NestedIntArray [][]int64 `gorm:"type:integer[3][3]"`
|
|
}
|
|
|
|
var err error
|
|
DB.Migrator().DropTable(&ArrayTypeModel{})
|
|
|
|
err = DB.AutoMigrate(&ArrayTypeModel{})
|
|
AssertEqual(t, nil, err)
|
|
|
|
ct, err := findColumnType(&ArrayTypeModel{}, "number")
|
|
AssertEqual(t, nil, err)
|
|
AssertEqual(t, "varchar", ct.DatabaseTypeName())
|
|
|
|
ct, err = findColumnType(&ArrayTypeModel{}, "text_array")
|
|
AssertEqual(t, nil, err)
|
|
AssertEqual(t, "text[]", ct.DatabaseTypeName())
|
|
|
|
ct, err = findColumnType(&ArrayTypeModel{}, "nested_text_array")
|
|
AssertEqual(t, nil, err)
|
|
AssertEqual(t, "text[]", ct.DatabaseTypeName())
|
|
|
|
ct, err = findColumnType(&ArrayTypeModel{}, "nested_int_array")
|
|
AssertEqual(t, nil, err)
|
|
AssertEqual(t, "integer[]", ct.DatabaseTypeName())
|
|
}
|
|
|
|
type mockMigrator struct {
|
|
gorm.Migrator
|
|
}
|
|
|
|
func (mm mockMigrator) AlterColumn(dst interface{}, field string) error {
|
|
err := mm.Migrator.AlterColumn(dst, field)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("trigger alter column error, field: %s", field)
|
|
}
|
|
|
|
func TestMigrateDonotAlterColumn(t *testing.T) {
|
|
wrapMockMigrator := func(m gorm.Migrator) mockMigrator {
|
|
return mockMigrator{
|
|
Migrator: m,
|
|
}
|
|
}
|
|
m := DB.Migrator()
|
|
mockM := wrapMockMigrator(m)
|
|
|
|
type NotTriggerUpdate struct {
|
|
ID uint
|
|
F1 uint16
|
|
F2 uint32
|
|
F3 int
|
|
F4 int64
|
|
F5 string
|
|
F6 float32
|
|
F7 float64
|
|
F8 time.Time
|
|
F9 bool
|
|
F10 []byte
|
|
}
|
|
|
|
var err error
|
|
err = mockM.DropTable(&NotTriggerUpdate{})
|
|
AssertEqual(t, err, nil)
|
|
err = mockM.AutoMigrate(&NotTriggerUpdate{})
|
|
AssertEqual(t, err, nil)
|
|
err = mockM.AutoMigrate(&NotTriggerUpdate{})
|
|
AssertEqual(t, err, nil)
|
|
}
|
|
|
|
func TestMigrateSameEmbeddedFieldName(t *testing.T) {
|
|
type UserStat struct {
|
|
GroundDestroyCount int
|
|
}
|
|
|
|
type GameUser struct {
|
|
gorm.Model
|
|
StatAb UserStat `gorm:"embedded;embeddedPrefix:stat_ab_"`
|
|
}
|
|
|
|
type UserStat1 struct {
|
|
GroundDestroyCount string
|
|
}
|
|
|
|
type GroundRate struct {
|
|
GroundDestroyCount int
|
|
}
|
|
|
|
type GameUser1 struct {
|
|
gorm.Model
|
|
StatAb UserStat1 `gorm:"embedded;embeddedPrefix:stat_ab_"`
|
|
GroundRateRb GroundRate `gorm:"embedded;embeddedPrefix:rate_ground_rb_"`
|
|
}
|
|
|
|
DB.Migrator().DropTable(&GameUser{})
|
|
err := DB.AutoMigrate(&GameUser{})
|
|
AssertEqual(t, nil, err)
|
|
|
|
err = DB.Table("game_users").AutoMigrate(&GameUser1{})
|
|
AssertEqual(t, nil, err)
|
|
|
|
_, err = findColumnType(&GameUser{}, "stat_ab_ground_destory_count")
|
|
AssertEqual(t, nil, err)
|
|
|
|
_, err = findColumnType(&GameUser{}, "rate_ground_rb_ground_destory_count")
|
|
AssertEqual(t, nil, err)
|
|
}
|
|
|
|
func TestMigrateDefaultNullString(t *testing.T) {
|
|
if DB.Dialector.Name() == "sqlserver" {
|
|
// sqlserver driver treats NULL and 'NULL' the same
|
|
t.Skip("skip sqlserver")
|
|
}
|
|
|
|
type NullModel struct {
|
|
ID uint
|
|
Content string `gorm:"default:null"`
|
|
}
|
|
|
|
type NullStringModel struct {
|
|
ID uint
|
|
Content string `gorm:"default:'null'"`
|
|
}
|
|
|
|
tableName := "null_string_model"
|
|
|
|
DB.Migrator().DropTable(tableName)
|
|
|
|
err := DB.Table(tableName).AutoMigrate(&NullModel{})
|
|
AssertEqual(t, err, nil)
|
|
|
|
// default null -> 'null'
|
|
err = DB.Table(tableName).AutoMigrate(&NullStringModel{})
|
|
AssertEqual(t, err, nil)
|
|
|
|
columnType, err := findColumnType(tableName, "content")
|
|
AssertEqual(t, err, nil)
|
|
|
|
defVal, ok := columnType.DefaultValue()
|
|
AssertEqual(t, defVal, "null")
|
|
AssertEqual(t, ok, true)
|
|
|
|
// default 'null' -> 'null'
|
|
session := DB.Session(&gorm.Session{Logger: Tracer{
|
|
Logger: DB.Config.Logger,
|
|
Test: func(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
|
sql, _ := fc()
|
|
if strings.HasPrefix(sql, "ALTER TABLE") {
|
|
t.Errorf("shouldn't execute: sql=%s", sql)
|
|
}
|
|
},
|
|
}})
|
|
err = session.Table(tableName).AutoMigrate(&NullStringModel{})
|
|
AssertEqual(t, err, nil)
|
|
|
|
columnType, err = findColumnType(tableName, "content")
|
|
AssertEqual(t, err, nil)
|
|
|
|
defVal, ok = columnType.DefaultValue()
|
|
AssertEqual(t, defVal, "null")
|
|
AssertEqual(t, ok, true)
|
|
|
|
// default 'null' -> null
|
|
err = DB.Table(tableName).AutoMigrate(&NullModel{})
|
|
AssertEqual(t, err, nil)
|
|
|
|
columnType, err = findColumnType(tableName, "content")
|
|
AssertEqual(t, err, nil)
|
|
|
|
defVal, ok = columnType.DefaultValue()
|
|
AssertEqual(t, defVal, "")
|
|
AssertEqual(t, ok, false)
|
|
}
|
|
|
|
func TestMigrateMySQLWithCustomizedTypes(t *testing.T) {
|
|
if DB.Dialector.Name() != "mysql" {
|
|
t.Skip()
|
|
}
|
|
|
|
type MyTable struct {
|
|
Def string `gorm:"size:512;index:idx_def,unique"`
|
|
Abc string `gorm:"size:65000000"`
|
|
}
|
|
|
|
DB.Migrator().DropTable("my_tables")
|
|
|
|
sql := "CREATE TABLE `my_tables` (`def` varchar(512),`abc` longtext,UNIQUE INDEX `idx_def` (`def`))"
|
|
if err := DB.Exec(sql).Error; err != nil {
|
|
t.Errorf("Failed, got error: %v", err)
|
|
}
|
|
|
|
session := DB.Session(&gorm.Session{Logger: Tracer{
|
|
Logger: DB.Config.Logger,
|
|
Test: func(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
|
sql, _ := fc()
|
|
if strings.HasPrefix(sql, "ALTER TABLE") {
|
|
t.Errorf("shouldn't execute: sql=%s", sql)
|
|
}
|
|
},
|
|
}})
|
|
|
|
if err := session.AutoMigrate(&MyTable{}); err != nil {
|
|
t.Errorf("Failed, got error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMigrateIgnoreRelations(t *testing.T) {
|
|
type RelationModel1 struct {
|
|
ID uint
|
|
}
|
|
type RelationModel2 struct {
|
|
ID uint
|
|
}
|
|
type RelationModel3 struct {
|
|
ID uint
|
|
RelationModel1ID uint
|
|
RelationModel1 *RelationModel1
|
|
RelationModel2ID uint
|
|
RelationModel2 *RelationModel2 `gorm:"-:migration"`
|
|
}
|
|
|
|
var err error
|
|
_ = DB.Migrator().DropTable(&RelationModel1{}, &RelationModel2{}, &RelationModel3{})
|
|
|
|
tx := DB.Session(&gorm.Session{})
|
|
tx.IgnoreRelationshipsWhenMigrating = true
|
|
|
|
err = tx.AutoMigrate(&RelationModel3{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
// RelationModel3 should be existed
|
|
_, err = findColumnType(&RelationModel3{}, "id")
|
|
AssertEqual(t, nil, err)
|
|
|
|
// RelationModel1 should not be existed
|
|
_, err = findColumnType(&RelationModel1{}, "id")
|
|
if err == nil {
|
|
t.Errorf("RelationModel1 should not be migrated")
|
|
}
|
|
|
|
// RelationModel2 should not be existed
|
|
_, err = findColumnType(&RelationModel2{}, "id")
|
|
if err == nil {
|
|
t.Errorf("RelationModel2 should not be migrated")
|
|
}
|
|
|
|
tx.IgnoreRelationshipsWhenMigrating = false
|
|
|
|
err = tx.AutoMigrate(&RelationModel3{})
|
|
if err != nil {
|
|
t.Errorf("AutoMigrate err:%v", err)
|
|
}
|
|
|
|
// RelationModel3 should be existed
|
|
_, err = findColumnType(&RelationModel3{}, "id")
|
|
AssertEqual(t, nil, err)
|
|
|
|
// RelationModel1 should be existed
|
|
_, err = findColumnType(&RelationModel1{}, "id")
|
|
AssertEqual(t, nil, err)
|
|
|
|
// RelationModel2 should not be existed
|
|
_, err = findColumnType(&RelationModel2{}, "id")
|
|
if err == nil {
|
|
t.Errorf("RelationModel2 should not be migrated")
|
|
}
|
|
}
|
|
|
|
func TestMigrateView(t *testing.T) {
|
|
DB.Save(GetUser("joins-args-db", Config{Pets: 2}))
|
|
|
|
if err := DB.Migrator().CreateView("invalid_users_pets",
|
|
gorm.ViewOption{Query: nil}); err != gorm.ErrSubQueryRequired {
|
|
t.Fatalf("no view should be created, got %v", err)
|
|
}
|
|
|
|
query := DB.Model(&User{}).
|
|
Select("users.id as users_id, users.name as users_name, pets.id as pets_id, pets.name as pets_name").
|
|
Joins("inner join pets on pets.user_id = users.id")
|
|
|
|
if err := DB.Migrator().CreateView("users_pets", gorm.ViewOption{Query: query}); err != nil {
|
|
t.Fatalf("Failed to crate view, got %v", err)
|
|
}
|
|
|
|
var count int64
|
|
if err := DB.Table("users_pets").Count(&count).Error; err != nil {
|
|
t.Fatalf("should found created view")
|
|
}
|
|
|
|
if err := DB.Migrator().DropView("users_pets"); err != nil {
|
|
t.Fatalf("Failed to drop view, got %v", err)
|
|
}
|
|
|
|
query = DB.Model(&User{}).Where("age > ?", 20)
|
|
if err := DB.Migrator().CreateView("users_view", gorm.ViewOption{Query: query}); err != nil {
|
|
t.Fatalf("Failed to crate view, got %v", err)
|
|
}
|
|
if err := DB.Migrator().DropView("users_view"); err != nil {
|
|
t.Fatalf("Failed to drop view, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMigrateExistingBoolColumnPG(t *testing.T) {
|
|
if DB.Dialector.Name() != "postgres" {
|
|
return
|
|
}
|
|
|
|
type ColumnStruct struct {
|
|
gorm.Model
|
|
Name string
|
|
StringBool string
|
|
SmallintBool int `gorm:"type:smallint"`
|
|
}
|
|
|
|
type ColumnStruct2 struct {
|
|
gorm.Model
|
|
Name string
|
|
StringBool bool // change existing boolean column from string to boolean
|
|
SmallintBool bool // change existing boolean column from smallint or other to boolean
|
|
}
|
|
|
|
DB.Migrator().DropTable(&ColumnStruct{})
|
|
|
|
if err := DB.AutoMigrate(&ColumnStruct{}); err != nil {
|
|
t.Errorf("Failed to migrate, 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 "string_bool":
|
|
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)
|
|
}
|
|
case "smallint_bool":
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTableType(t *testing.T) {
|
|
// currently it is only supported for mysql driver
|
|
if !isMysql() {
|
|
return
|
|
}
|
|
|
|
const tblName = "cities"
|
|
const tblSchema = "gorm"
|
|
const tblType = "BASE TABLE"
|
|
const tblComment = "foobar comment"
|
|
|
|
type City struct {
|
|
gorm.Model
|
|
Name string `gorm:"unique"`
|
|
}
|
|
|
|
DB.Migrator().DropTable(&City{})
|
|
|
|
if err := DB.Set("gorm:table_options",
|
|
fmt.Sprintf("ENGINE InnoDB COMMENT '%s'", tblComment)).AutoMigrate(&City{}); err != nil {
|
|
t.Fatalf("failed to migrate cities tables, got error: %v", err)
|
|
}
|
|
|
|
tableType, err := DB.Table("cities").Migrator().TableType(&City{})
|
|
if err != nil {
|
|
t.Fatalf("failed to get table type, got error %v", err)
|
|
}
|
|
|
|
if tableType.Schema() != tblSchema {
|
|
t.Fatalf("expected tblSchema to be %s but got %s", tblSchema, tableType.Schema())
|
|
}
|
|
|
|
if tableType.Name() != tblName {
|
|
t.Fatalf("expected table name to be %s but got %s", tblName, tableType.Name())
|
|
}
|
|
|
|
if tableType.Type() != tblType {
|
|
t.Fatalf("expected table type to be %s but got %s", tblType, tableType.Type())
|
|
}
|
|
|
|
comment, ok := tableType.Comment()
|
|
if !ok || comment != tblComment {
|
|
t.Fatalf("expected comment %s got %s", tblComment, comment)
|
|
}
|
|
}
|
|
|
|
func TestMigrateWithUniqueIndexAndUnique(t *testing.T) {
|
|
const table = "unique_struct"
|
|
|
|
checkField := func(model interface{}, fieldName string, unique bool, uniqueIndex string) {
|
|
stmt := &gorm.Statement{DB: DB}
|
|
err := stmt.Parse(model)
|
|
if err != nil {
|
|
t.Fatalf("%v: failed to parse schema, got error: %v", utils.FileWithLineNum(), err)
|
|
}
|
|
_ = stmt.Schema.ParseIndexes()
|
|
field := stmt.Schema.LookUpField(fieldName)
|
|
if field == nil {
|
|
t.Fatalf("%v: failed to find column %q", utils.FileWithLineNum(), fieldName)
|
|
}
|
|
if field.Unique != unique {
|
|
t.Fatalf("%v: %q column %q unique should be %v but got %v", utils.FileWithLineNum(), stmt.Schema.Table, fieldName, unique, field.Unique)
|
|
}
|
|
if field.UniqueIndex != uniqueIndex {
|
|
t.Fatalf("%v: %q column %q uniqueIndex should be %v but got %v", utils.FileWithLineNum(), stmt.Schema, fieldName, uniqueIndex, field.UniqueIndex)
|
|
}
|
|
}
|
|
|
|
type ( // not unique
|
|
UniqueStruct1 struct {
|
|
Name string `gorm:"size:10"`
|
|
}
|
|
UniqueStruct2 struct {
|
|
Name string `gorm:"size:20"`
|
|
}
|
|
)
|
|
checkField(&UniqueStruct1{}, "name", false, "")
|
|
checkField(&UniqueStruct2{}, "name", false, "")
|
|
|
|
type ( // unique
|
|
UniqueStruct3 struct {
|
|
Name string `gorm:"size:30;unique"`
|
|
}
|
|
UniqueStruct4 struct {
|
|
Name string `gorm:"size:40;unique"`
|
|
}
|
|
)
|
|
checkField(&UniqueStruct3{}, "name", true, "")
|
|
checkField(&UniqueStruct4{}, "name", true, "")
|
|
|
|
type ( // uniqueIndex
|
|
UniqueStruct5 struct {
|
|
Name string `gorm:"size:50;uniqueIndex"`
|
|
}
|
|
UniqueStruct6 struct {
|
|
Name string `gorm:"size:60;uniqueIndex"`
|
|
}
|
|
UniqueStruct7 struct {
|
|
Name string `gorm:"size:70;uniqueIndex:idx_us6_all_names"`
|
|
NickName string `gorm:"size:70;uniqueIndex:idx_us6_all_names"`
|
|
}
|
|
)
|
|
checkField(&UniqueStruct5{}, "name", false, "idx_unique_struct5_name")
|
|
checkField(&UniqueStruct6{}, "name", false, "idx_unique_struct6_name")
|
|
|
|
checkField(&UniqueStruct7{}, "name", false, "")
|
|
checkField(&UniqueStruct7{}, "nick_name", false, "")
|
|
checkField(&UniqueStruct7{}, "nick_name", false, "")
|
|
|
|
type UniqueStruct8 struct { // unique and uniqueIndex
|
|
Name string `gorm:"size:60;unique;index:my_us8_index,unique;"`
|
|
}
|
|
checkField(&UniqueStruct8{}, "name", true, "my_us8_index")
|
|
|
|
type TestCase struct {
|
|
name string
|
|
from, to interface{}
|
|
checkFunc func(t *testing.T)
|
|
}
|
|
|
|
checkColumnType := func(t *testing.T, fieldName string, unique bool) {
|
|
columnTypes, err := DB.Migrator().ColumnTypes(table)
|
|
if err != nil {
|
|
t.Fatalf("%v: failed to get column types, got error: %v", utils.FileWithLineNum(), err)
|
|
}
|
|
var found gorm.ColumnType
|
|
for _, columnType := range columnTypes {
|
|
if columnType.Name() == fieldName {
|
|
found = columnType
|
|
}
|
|
}
|
|
if found == nil {
|
|
t.Fatalf("%v: failed to find column type %q", utils.FileWithLineNum(), fieldName)
|
|
}
|
|
if actualUnique, ok := found.Unique(); !ok || actualUnique != unique {
|
|
t.Fatalf("%v: column %q unique should be %v but got %v", utils.FileWithLineNum(), fieldName, unique, actualUnique)
|
|
}
|
|
}
|
|
|
|
checkIndex := func(t *testing.T, expected []gorm.Index) {
|
|
indexes, err := DB.Migrator().GetIndexes(table)
|
|
if err != nil {
|
|
t.Fatalf("%v: failed to get indexes, got error: %v", utils.FileWithLineNum(), err)
|
|
}
|
|
assert.ElementsMatch(t, expected, indexes)
|
|
}
|
|
|
|
uniqueIndex := &migrator.Index{TableName: table, NameValue: DB.Config.NamingStrategy.IndexName(table, "name"), ColumnList: []string{"name"}, PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}}
|
|
myIndex := &migrator.Index{TableName: table, NameValue: "my_us8_index", ColumnList: []string{"name"}, PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}}
|
|
mulIndex := &migrator.Index{TableName: table, NameValue: "idx_us6_all_names", ColumnList: []string{"name", "nick_name"}, PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}}
|
|
|
|
var checkNotUnique, checkUnique, checkUniqueIndex, checkMyIndex, checkMulIndex func(t *testing.T)
|
|
// UniqueAffectedByUniqueIndex is true
|
|
if DB.Dialector.Name() == "mysql" {
|
|
uniqueConstraintIndex := &migrator.Index{TableName: table, NameValue: DB.Config.NamingStrategy.UniqueName(table, "name"), ColumnList: []string{"name"}, PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}}
|
|
checkNotUnique = func(t *testing.T) {
|
|
checkColumnType(t, "name", false)
|
|
checkIndex(t, nil)
|
|
}
|
|
checkUnique = func(t *testing.T) {
|
|
checkColumnType(t, "name", true)
|
|
checkIndex(t, []gorm.Index{uniqueConstraintIndex})
|
|
}
|
|
checkUniqueIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", true)
|
|
checkIndex(t, []gorm.Index{uniqueIndex})
|
|
}
|
|
checkMyIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", true)
|
|
checkIndex(t, []gorm.Index{uniqueConstraintIndex, myIndex})
|
|
}
|
|
checkMulIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", false)
|
|
checkColumnType(t, "nick_name", false)
|
|
checkIndex(t, []gorm.Index{mulIndex})
|
|
}
|
|
} else {
|
|
checkNotUnique = func(t *testing.T) { checkColumnType(t, "name", false) }
|
|
checkUnique = func(t *testing.T) { checkColumnType(t, "name", true) }
|
|
checkUniqueIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", false)
|
|
checkIndex(t, []gorm.Index{uniqueIndex})
|
|
}
|
|
checkMyIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", true)
|
|
if !DB.Migrator().HasIndex(table, myIndex.Name()) {
|
|
t.Errorf("%v: should has index %s but not", utils.FileWithLineNum(), myIndex.Name())
|
|
}
|
|
}
|
|
checkMulIndex = func(t *testing.T) {
|
|
checkColumnType(t, "name", false)
|
|
checkColumnType(t, "nick_name", false)
|
|
if !DB.Migrator().HasIndex(table, mulIndex.Name()) {
|
|
t.Errorf("%v: should has index %s but not", utils.FileWithLineNum(), mulIndex.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
tests := []TestCase{
|
|
{name: "notUnique to notUnique", from: &UniqueStruct1{}, to: &UniqueStruct2{}, checkFunc: checkNotUnique},
|
|
{name: "notUnique to unique", from: &UniqueStruct1{}, to: &UniqueStruct3{}, checkFunc: checkUnique},
|
|
{name: "notUnique to uniqueIndex", from: &UniqueStruct1{}, to: &UniqueStruct5{}, checkFunc: checkUniqueIndex},
|
|
{name: "notUnique to uniqueAndUniqueIndex", from: &UniqueStruct1{}, to: &UniqueStruct8{}, checkFunc: checkMyIndex},
|
|
{name: "unique to unique", from: &UniqueStruct3{}, to: &UniqueStruct4{}, checkFunc: checkUnique},
|
|
{name: "unique to uniqueIndex", from: &UniqueStruct3{}, to: &UniqueStruct5{}, checkFunc: checkUniqueIndex},
|
|
{name: "unique to uniqueAndUniqueIndex", from: &UniqueStruct3{}, to: &UniqueStruct8{}, checkFunc: checkMyIndex},
|
|
{name: "uniqueIndex to uniqueIndex", from: &UniqueStruct5{}, to: &UniqueStruct6{}, checkFunc: checkUniqueIndex},
|
|
{name: "uniqueIndex to uniqueAndUniqueIndex", from: &UniqueStruct5{}, to: &UniqueStruct8{}, checkFunc: checkMyIndex},
|
|
{name: "uniqueIndex to multi uniqueIndex", from: &UniqueStruct5{}, to: &UniqueStruct7{}, checkFunc: checkMulIndex},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := DB.Migrator().DropTable(table); err != nil {
|
|
t.Fatalf("failed to drop table, got error: %v", err)
|
|
}
|
|
if err := DB.Table(table).AutoMigrate(test.from); err != nil {
|
|
t.Fatalf("failed to migrate table, got error: %v", err)
|
|
}
|
|
if err := DB.Table(table).AutoMigrate(test.to); err != nil {
|
|
t.Fatalf("failed to migrate table, got error: %v", err)
|
|
}
|
|
test.checkFunc(t)
|
|
})
|
|
}
|
|
|
|
if DB.Dialector.Name() != "sqlserver" {
|
|
// In SQLServer, If an index or constraint depends on the column,
|
|
// this column will not be able to run ALTER
|
|
// see https://stackoverflow.com/questions/19460912/the-object-df-is-dependent-on-column-changing-int-to-double/19461205#19461205
|
|
// may we need to create another PR to fix it, see https://github.com/go-gorm/sqlserver/pull/106
|
|
tests = []TestCase{
|
|
{name: "unique to notUnique", from: &UniqueStruct3{}, to: &UniqueStruct1{}, checkFunc: checkNotUnique},
|
|
{name: "uniqueIndex to notUnique", from: &UniqueStruct5{}, to: &UniqueStruct2{}, checkFunc: checkNotUnique},
|
|
{name: "uniqueIndex to unique", from: &UniqueStruct5{}, to: &UniqueStruct3{}, checkFunc: checkUnique},
|
|
}
|
|
}
|
|
|
|
if DB.Dialector.Name() == "mysql" {
|
|
compatibilityTests := []TestCase{
|
|
{name: "oldUnique to notUnique", to: UniqueStruct1{}, checkFunc: checkNotUnique},
|
|
{name: "oldUnique to unique", to: UniqueStruct3{}, checkFunc: checkUnique},
|
|
{name: "oldUnique to uniqueIndex", to: UniqueStruct5{}, checkFunc: checkUniqueIndex},
|
|
{name: "oldUnique to uniqueAndUniqueIndex", to: UniqueStruct8{}, checkFunc: checkMyIndex},
|
|
}
|
|
for _, test := range compatibilityTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := DB.Migrator().DropTable(table); err != nil {
|
|
t.Fatalf("failed to drop table, got error: %v", err)
|
|
}
|
|
if err := DB.Exec("CREATE TABLE ? (`name` varchar(10) UNIQUE)", clause.Table{Name: table}).Error; err != nil {
|
|
t.Fatalf("failed to create table, got error: %v", err)
|
|
}
|
|
if err := DB.Table(table).AutoMigrate(test.to); err != nil {
|
|
t.Fatalf("failed to migrate table, got error: %v", err)
|
|
}
|
|
test.checkFunc(t)
|
|
})
|
|
}
|
|
}
|
|
}
|