123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- package gorm_test
- import (
- "database/sql"
- "database/sql/driver"
- "errors"
- "fmt"
- "os"
- "reflect"
- "strconv"
- "testing"
- "time"
- "github.com/jinzhu/gorm"
- )
- type User struct {
- Id int64
- Age int64
- UserNum Num
- Name string `sql:"size:255"`
- Email string
- Birthday *time.Time // Time
- CreatedAt time.Time // CreatedAt: Time of record is created, will be insert automatically
- UpdatedAt time.Time // UpdatedAt: Time of record is updated, will be updated automatically
- Emails []Email // Embedded structs
- BillingAddress Address // Embedded struct
- BillingAddressID sql.NullInt64 // Embedded struct's foreign key
- ShippingAddress Address // Embedded struct
- ShippingAddressId int64 // Embedded struct's foreign key
- CreditCard CreditCard
- Latitude float64
- Languages []Language `gorm:"many2many:user_languages;"`
- CompanyID *int
- Company Company
- Role Role
- Password EncryptedData
- PasswordHash []byte
- IgnoreMe int64 `sql:"-"`
- IgnoreStringSlice []string `sql:"-"`
- Ignored struct{ Name string } `sql:"-"`
- IgnoredPointer *User `sql:"-"`
- }
- type NotSoLongTableName struct {
- Id int64
- ReallyLongThingID int64
- ReallyLongThing ReallyLongTableNameToTestMySQLNameLengthLimit
- }
- type ReallyLongTableNameToTestMySQLNameLengthLimit struct {
- Id int64
- }
- type ReallyLongThingThatReferencesShort struct {
- Id int64
- ShortID int64
- Short Short
- }
- type Short struct {
- Id int64
- }
- type CreditCard struct {
- ID int8
- Number string
- UserId sql.NullInt64
- CreatedAt time.Time `sql:"not null"`
- UpdatedAt time.Time
- DeletedAt *time.Time `sql:"column:deleted_time"`
- }
- type Email struct {
- Id int16
- UserId int
- Email string `sql:"type:varchar(100);"`
- CreatedAt time.Time
- UpdatedAt time.Time
- }
- type Address struct {
- ID int
- Address1 string
- Address2 string
- Post string
- CreatedAt time.Time
- UpdatedAt time.Time
- DeletedAt *time.Time
- }
- type Language struct {
- gorm.Model
- Name string
- Users []User `gorm:"many2many:user_languages;"`
- }
- type Product struct {
- Id int64
- Code string
- Price int64
- CreatedAt time.Time
- UpdatedAt time.Time
- AfterFindCallTimes int64
- BeforeCreateCallTimes int64
- AfterCreateCallTimes int64
- BeforeUpdateCallTimes int64
- AfterUpdateCallTimes int64
- BeforeSaveCallTimes int64
- AfterSaveCallTimes int64
- BeforeDeleteCallTimes int64
- AfterDeleteCallTimes int64
- }
- type Company struct {
- Id int64
- Name string
- Owner *User `sql:"-"`
- }
- type Place struct {
- Id int64
- PlaceAddressID int
- PlaceAddress *Address `gorm:"save_associations:false"`
- OwnerAddressID int
- OwnerAddress *Address `gorm:"save_associations:true"`
- }
- type EncryptedData []byte
- func (data *EncryptedData) Scan(value interface{}) error {
- if b, ok := value.([]byte); ok {
- if len(b) < 3 || b[0] != '*' || b[1] != '*' || b[2] != '*' {
- return errors.New("Too short")
- }
- *data = b[3:]
- return nil
- }
- return errors.New("Bytes expected")
- }
- func (data EncryptedData) Value() (driver.Value, error) {
- if len(data) > 0 && data[0] == 'x' {
- //needed to test failures
- return nil, errors.New("Should not start with 'x'")
- }
- //prepend asterisks
- return append([]byte("***"), data...), nil
- }
- type Role struct {
- Name string `gorm:"size:256"`
- }
- func (role *Role) Scan(value interface{}) error {
- if b, ok := value.([]uint8); ok {
- role.Name = string(b)
- } else {
- role.Name = value.(string)
- }
- return nil
- }
- func (role Role) Value() (driver.Value, error) {
- return role.Name, nil
- }
- func (role Role) IsAdmin() bool {
- return role.Name == "admin"
- }
- type Num int64
- func (i *Num) Scan(src interface{}) error {
- switch s := src.(type) {
- case []byte:
- n, _ := strconv.Atoi(string(s))
- *i = Num(n)
- case int64:
- *i = Num(s)
- default:
- return errors.New("Cannot scan NamedInt from " + reflect.ValueOf(src).String())
- }
- return nil
- }
- type Animal struct {
- Counter uint64 `gorm:"primary_key:yes"`
- Name string `sql:"DEFAULT:'galeone'"`
- From string //test reserved sql keyword as field name
- Age time.Time `sql:"DEFAULT:current_timestamp"`
- unexported string // unexported value
- CreatedAt time.Time
- UpdatedAt time.Time
- }
- type JoinTable struct {
- From uint64
- To uint64
- Time time.Time `sql:"default: null"`
- }
- type Post struct {
- Id int64
- CategoryId sql.NullInt64
- MainCategoryId int64
- Title string
- Body string
- Comments []*Comment
- Category Category
- MainCategory Category
- }
- type Category struct {
- gorm.Model
- Name string
- Categories []Category
- CategoryID *uint
- }
- type Comment struct {
- gorm.Model
- PostId int64
- Content string
- Post Post
- }
- // Scanner
- type NullValue struct {
- Id int64
- Name sql.NullString `sql:"not null"`
- Gender *sql.NullString `sql:"not null"`
- Age sql.NullInt64
- Male sql.NullBool
- Height sql.NullFloat64
- AddedAt NullTime
- }
- type NullTime struct {
- Time time.Time
- Valid bool
- }
- func (nt *NullTime) Scan(value interface{}) error {
- if value == nil {
- nt.Valid = false
- return nil
- }
- nt.Time, nt.Valid = value.(time.Time), true
- return nil
- }
- func (nt NullTime) Value() (driver.Value, error) {
- if !nt.Valid {
- return nil, nil
- }
- return nt.Time, nil
- }
- func getPreparedUser(name string, role string) *User {
- var company Company
- DB.Where(Company{Name: role}).FirstOrCreate(&company)
- return &User{
- Name: name,
- Age: 20,
- Role: Role{role},
- BillingAddress: Address{Address1: fmt.Sprintf("Billing Address %v", name)},
- ShippingAddress: Address{Address1: fmt.Sprintf("Shipping Address %v", name)},
- CreditCard: CreditCard{Number: fmt.Sprintf("123456%v", name)},
- Emails: []Email{
- {Email: fmt.Sprintf("user_%v@example1.com", name)}, {Email: fmt.Sprintf("user_%v@example2.com", name)},
- },
- Company: company,
- Languages: []Language{
- {Name: fmt.Sprintf("lang_1_%v", name)},
- {Name: fmt.Sprintf("lang_2_%v", name)},
- },
- }
- }
- func runMigration() {
- if err := DB.DropTableIfExists(&User{}).Error; err != nil {
- fmt.Printf("Got error when try to delete table users, %+v\n", err)
- }
- for _, table := range []string{"animals", "user_languages"} {
- DB.Exec(fmt.Sprintf("drop table %v;", table))
- }
- values := []interface{}{&Short{}, &ReallyLongThingThatReferencesShort{}, &ReallyLongTableNameToTestMySQLNameLengthLimit{}, &NotSoLongTableName{}, &Product{}, &Email{}, &Address{}, &CreditCard{}, &Company{}, &Role{}, &Language{}, &HNPost{}, &EngadgetPost{}, &Animal{}, &User{}, &JoinTable{}, &Post{}, &Category{}, &Comment{}, &Cat{}, &Dog{}, &Hamster{}, &Toy{}, &ElementWithIgnoredField{}, &Place{}}
- for _, value := range values {
- DB.DropTable(value)
- }
- if err := DB.AutoMigrate(values...).Error; err != nil {
- panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
- }
- }
- func TestIndexes(t *testing.T) {
- if err := DB.Model(&Email{}).AddIndex("idx_email_email", "email").Error; err != nil {
- t.Errorf("Got error when tried to create index: %+v", err)
- }
- scope := DB.NewScope(&Email{})
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
- t.Errorf("Email should have index idx_email_email")
- }
- if err := DB.Model(&Email{}).RemoveIndex("idx_email_email").Error; err != nil {
- t.Errorf("Got error when tried to remove index: %+v", err)
- }
- if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
- t.Errorf("Email's index idx_email_email should be deleted")
- }
- if err := DB.Model(&Email{}).AddIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
- t.Errorf("Got error when tried to create index: %+v", err)
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
- t.Errorf("Email should have index idx_email_email_and_user_id")
- }
- if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
- t.Errorf("Got error when tried to remove index: %+v", err)
- }
- if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
- t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
- }
- if err := DB.Model(&Email{}).AddUniqueIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
- t.Errorf("Got error when tried to create index: %+v", err)
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
- t.Errorf("Email should have index idx_email_email_and_user_id")
- }
- if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.comiii"}, {Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error == nil {
- t.Errorf("Should get to create duplicate record when having unique index")
- }
- var user = User{Name: "sample_user"}
- DB.Save(&user)
- if DB.Model(&user).Association("Emails").Append(Email{Email: "not-1duplicated@gmail.com"}, Email{Email: "not-duplicated2@gmail.com"}).Error != nil {
- t.Errorf("Should get no error when append two emails for user")
- }
- if DB.Model(&user).Association("Emails").Append(Email{Email: "duplicated@gmail.com"}, Email{Email: "duplicated@gmail.com"}).Error == nil {
- t.Errorf("Should get no duplicated email error when insert duplicated emails for a user")
- }
- if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
- t.Errorf("Got error when tried to remove index: %+v", err)
- }
- if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
- t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
- }
- if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error != nil {
- t.Errorf("Should be able to create duplicated emails after remove unique index")
- }
- }
- type EmailWithIdx struct {
- Id int64
- UserId int64
- Email string `sql:"index:idx_email_agent"`
- UserAgent string `sql:"index:idx_email_agent"`
- RegisteredAt *time.Time `sql:"unique_index"`
- CreatedAt time.Time
- UpdatedAt time.Time
- }
- func TestAutoMigration(t *testing.T) {
- DB.AutoMigrate(&Address{})
- DB.DropTable(&EmailWithIdx{})
- if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
- t.Errorf("Auto Migrate should not raise any error")
- }
- now := time.Now()
- DB.Save(&EmailWithIdx{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: &now})
- scope := DB.NewScope(&EmailWithIdx{})
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_agent") {
- t.Errorf("Failed to create index")
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "uix_email_with_idxes_registered_at") {
- t.Errorf("Failed to create index")
- }
- var bigemail EmailWithIdx
- DB.First(&bigemail, "user_agent = ?", "pc")
- if bigemail.Email != "jinzhu@example.org" || bigemail.UserAgent != "pc" || bigemail.RegisteredAt.IsZero() {
- t.Error("Big Emails should be saved and fetched correctly")
- }
- }
- func TestCreateAndAutomigrateTransaction(t *testing.T) {
- tx := DB.Begin()
- func() {
- type Bar struct {
- ID uint
- }
- DB.DropTableIfExists(&Bar{})
- if ok := DB.HasTable("bars"); ok {
- t.Errorf("Table should not exist, but does")
- }
- if ok := tx.HasTable("bars"); ok {
- t.Errorf("Table should not exist, but does")
- }
- }()
- func() {
- type Bar struct {
- Name string
- }
- err := tx.CreateTable(&Bar{}).Error
- if err != nil {
- t.Errorf("Should have been able to create the table, but couldn't: %s", err)
- }
- if ok := tx.HasTable(&Bar{}); !ok {
- t.Errorf("The transaction should be able to see the table")
- }
- }()
- func() {
- type Bar struct {
- Stuff string
- }
- err := tx.AutoMigrate(&Bar{}).Error
- if err != nil {
- t.Errorf("Should have been able to alter the table, but couldn't")
- }
- }()
- tx.Rollback()
- }
- type MultipleIndexes struct {
- ID int64
- UserID int64 `sql:"unique_index:uix_multipleindexes_user_name,uix_multipleindexes_user_email;index:idx_multipleindexes_user_other"`
- Name string `sql:"unique_index:uix_multipleindexes_user_name"`
- Email string `sql:"unique_index:,uix_multipleindexes_user_email"`
- Other string `sql:"index:,idx_multipleindexes_user_other"`
- }
- func TestMultipleIndexes(t *testing.T) {
- if err := DB.DropTableIfExists(&MultipleIndexes{}).Error; err != nil {
- fmt.Printf("Got error when try to delete table multiple_indexes, %+v\n", err)
- }
- DB.AutoMigrate(&MultipleIndexes{})
- if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
- t.Errorf("Auto Migrate should not raise any error")
- }
- DB.Save(&MultipleIndexes{UserID: 1, Name: "jinzhu", Email: "jinzhu@example.org", Other: "foo"})
- scope := DB.NewScope(&MultipleIndexes{})
- if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_name") {
- t.Errorf("Failed to create index")
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_email") {
- t.Errorf("Failed to create index")
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "uix_multiple_indexes_email") {
- t.Errorf("Failed to create index")
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_multipleindexes_user_other") {
- t.Errorf("Failed to create index")
- }
- if !scope.Dialect().HasIndex(scope.TableName(), "idx_multiple_indexes_other") {
- t.Errorf("Failed to create index")
- }
- var mutipleIndexes MultipleIndexes
- DB.First(&mutipleIndexes, "name = ?", "jinzhu")
- if mutipleIndexes.Email != "jinzhu@example.org" || mutipleIndexes.Name != "jinzhu" {
- t.Error("MutipleIndexes should be saved and fetched correctly")
- }
- // Check unique constraints
- if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
- t.Error("MultipleIndexes unique index failed")
- }
- if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "foo@example.org", Other: "foo"}).Error; err != nil {
- t.Error("MultipleIndexes unique index failed")
- }
- if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
- t.Error("MultipleIndexes unique index failed")
- }
- if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "foo2@example.org", Other: "foo"}).Error; err != nil {
- t.Error("MultipleIndexes unique index failed")
- }
- }
- func TestModifyColumnType(t *testing.T) {
- if dialect := os.Getenv("GORM_DIALECT"); dialect != "postgres" && dialect != "mysql" && dialect != "mssql" {
- t.Skip("Skipping this because only postgres, mysql and mssql support altering a column type")
- }
- type ModifyColumnType struct {
- gorm.Model
- Name1 string `gorm:"length:100"`
- Name2 string `gorm:"length:200"`
- }
- DB.DropTable(&ModifyColumnType{})
- DB.CreateTable(&ModifyColumnType{})
- name2Field, _ := DB.NewScope(&ModifyColumnType{}).FieldByName("Name2")
- name2Type := DB.Dialect().DataTypeOf(name2Field.StructField)
- if err := DB.Model(&ModifyColumnType{}).ModifyColumn("name1", name2Type).Error; err != nil {
- t.Errorf("No error should happen when ModifyColumn, but got %v", err)
- }
- }
|