14 "github.com/jinzhu/gorm"
21 Name string `sql:"size:255"`
23 Birthday *time.Time // Time
24 CreatedAt time.Time // CreatedAt: Time of record is created, will be insert automatically
25 UpdatedAt time.Time // UpdatedAt: Time of record is updated, will be updated automatically
26 Emails []Email // Embedded structs
27 BillingAddress Address // Embedded struct
28 BillingAddressID sql.NullInt64 // Embedded struct's foreign key
29 ShippingAddress Address // Embedded struct
30 ShippingAddressId int64 // Embedded struct's foreign key
33 Languages []Language `gorm:"many2many:user_languages;"`
37 Password EncryptedData
39 IgnoreMe int64 `sql:"-"`
40 IgnoreStringSlice []string `sql:"-"`
41 Ignored struct{ Name string } `sql:"-"`
42 IgnoredPointer *User `sql:"-"`
45 type NotSoLongTableName struct {
47 ReallyLongThingID int64
48 ReallyLongThing ReallyLongTableNameToTestMySQLNameLengthLimit
51 type ReallyLongTableNameToTestMySQLNameLengthLimit struct {
55 type ReallyLongThingThatReferencesShort struct {
65 type CreditCard struct {
69 CreatedAt time.Time `sql:"not null"`
71 DeletedAt *time.Time `sql:"column:deleted_time"`
77 Email string `sql:"type:varchar(100);"`
92 type Language struct {
95 Users []User `gorm:"many2many:user_languages;"`
104 AfterFindCallTimes int64
105 BeforeCreateCallTimes int64
106 AfterCreateCallTimes int64
107 BeforeUpdateCallTimes int64
108 AfterUpdateCallTimes int64
109 BeforeSaveCallTimes int64
110 AfterSaveCallTimes int64
111 BeforeDeleteCallTimes int64
112 AfterDeleteCallTimes int64
115 type Company struct {
118 Owner *User `sql:"-"`
124 PlaceAddress *Address `gorm:"save_associations:false"`
126 OwnerAddress *Address `gorm:"save_associations:true"`
129 type EncryptedData []byte
131 func (data *EncryptedData) Scan(value interface{}) error {
132 if b, ok := value.([]byte); ok {
133 if len(b) < 3 || b[0] != '*' || b[1] != '*' || b[2] != '*' {
134 return errors.New("Too short")
141 return errors.New("Bytes expected")
144 func (data EncryptedData) Value() (driver.Value, error) {
145 if len(data) > 0 && data[0] == 'x' {
146 //needed to test failures
147 return nil, errors.New("Should not start with 'x'")
151 return append([]byte("***"), data...), nil
155 Name string `gorm:"size:256"`
158 func (role *Role) Scan(value interface{}) error {
159 if b, ok := value.([]uint8); ok {
160 role.Name = string(b)
162 role.Name = value.(string)
167 func (role Role) Value() (driver.Value, error) {
168 return role.Name, nil
171 func (role Role) IsAdmin() bool {
172 return role.Name == "admin"
177 func (i *Num) Scan(src interface{}) error {
178 switch s := src.(type) {
180 n, _ := strconv.Atoi(string(s))
185 return errors.New("Cannot scan NamedInt from " + reflect.ValueOf(src).String())
191 Counter uint64 `gorm:"primary_key:yes"`
192 Name string `sql:"DEFAULT:'galeone'"`
193 From string //test reserved sql keyword as field name
194 Age time.Time `sql:"DEFAULT:current_timestamp"`
195 unexported string // unexported value
200 type JoinTable struct {
203 Time time.Time `sql:"default: null"`
208 CategoryId sql.NullInt64
214 MainCategory Category
217 type Category struct {
221 Categories []Category
225 type Comment struct {
233 type NullValue struct {
235 Name sql.NullString `sql:"not null"`
236 Gender *sql.NullString `sql:"not null"`
239 Height sql.NullFloat64
243 type NullTime struct {
248 func (nt *NullTime) Scan(value interface{}) error {
253 nt.Time, nt.Valid = value.(time.Time), true
257 func (nt NullTime) Value() (driver.Value, error) {
264 func getPreparedUser(name string, role string) *User {
266 DB.Where(Company{Name: role}).FirstOrCreate(&company)
272 BillingAddress: Address{Address1: fmt.Sprintf("Billing Address %v", name)},
273 ShippingAddress: Address{Address1: fmt.Sprintf("Shipping Address %v", name)},
274 CreditCard: CreditCard{Number: fmt.Sprintf("123456%v", name)},
276 {Email: fmt.Sprintf("user_%v@example1.com", name)}, {Email: fmt.Sprintf("user_%v@example2.com", name)},
279 Languages: []Language{
280 {Name: fmt.Sprintf("lang_1_%v", name)},
281 {Name: fmt.Sprintf("lang_2_%v", name)},
286 func runMigration() {
287 if err := DB.DropTableIfExists(&User{}).Error; err != nil {
288 fmt.Printf("Got error when try to delete table users, %+v\n", err)
291 for _, table := range []string{"animals", "user_languages"} {
292 DB.Exec(fmt.Sprintf("drop table %v;", table))
295 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{}}
296 for _, value := range values {
299 if err := DB.AutoMigrate(values...).Error; err != nil {
300 panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
304 func TestIndexes(t *testing.T) {
305 if err := DB.Model(&Email{}).AddIndex("idx_email_email", "email").Error; err != nil {
306 t.Errorf("Got error when tried to create index: %+v", err)
309 scope := DB.NewScope(&Email{})
310 if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
311 t.Errorf("Email should have index idx_email_email")
314 if err := DB.Model(&Email{}).RemoveIndex("idx_email_email").Error; err != nil {
315 t.Errorf("Got error when tried to remove index: %+v", err)
318 if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
319 t.Errorf("Email's index idx_email_email should be deleted")
322 if err := DB.Model(&Email{}).AddIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
323 t.Errorf("Got error when tried to create index: %+v", err)
326 if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
327 t.Errorf("Email should have index idx_email_email_and_user_id")
330 if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
331 t.Errorf("Got error when tried to remove index: %+v", err)
334 if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
335 t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
338 if err := DB.Model(&Email{}).AddUniqueIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
339 t.Errorf("Got error when tried to create index: %+v", err)
342 if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
343 t.Errorf("Email should have index idx_email_email_and_user_id")
346 if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.comiii"}, {Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error == nil {
347 t.Errorf("Should get to create duplicate record when having unique index")
350 var user = User{Name: "sample_user"}
352 if DB.Model(&user).Association("Emails").Append(Email{Email: "not-1duplicated@gmail.com"}, Email{Email: "not-duplicated2@gmail.com"}).Error != nil {
353 t.Errorf("Should get no error when append two emails for user")
356 if DB.Model(&user).Association("Emails").Append(Email{Email: "duplicated@gmail.com"}, Email{Email: "duplicated@gmail.com"}).Error == nil {
357 t.Errorf("Should get no duplicated email error when insert duplicated emails for a user")
360 if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
361 t.Errorf("Got error when tried to remove index: %+v", err)
364 if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
365 t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
368 if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error != nil {
369 t.Errorf("Should be able to create duplicated emails after remove unique index")
373 type EmailWithIdx struct {
376 Email string `sql:"index:idx_email_agent"`
377 UserAgent string `sql:"index:idx_email_agent"`
378 RegisteredAt *time.Time `sql:"unique_index"`
383 func TestAutoMigration(t *testing.T) {
384 DB.AutoMigrate(&Address{})
385 DB.DropTable(&EmailWithIdx{})
386 if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
387 t.Errorf("Auto Migrate should not raise any error")
391 DB.Save(&EmailWithIdx{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: &now})
393 scope := DB.NewScope(&EmailWithIdx{})
394 if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_agent") {
395 t.Errorf("Failed to create index")
398 if !scope.Dialect().HasIndex(scope.TableName(), "uix_email_with_idxes_registered_at") {
399 t.Errorf("Failed to create index")
402 var bigemail EmailWithIdx
403 DB.First(&bigemail, "user_agent = ?", "pc")
404 if bigemail.Email != "jinzhu@example.org" || bigemail.UserAgent != "pc" || bigemail.RegisteredAt.IsZero() {
405 t.Error("Big Emails should be saved and fetched correctly")
409 func TestCreateAndAutomigrateTransaction(t *testing.T) {
416 DB.DropTableIfExists(&Bar{})
418 if ok := DB.HasTable("bars"); ok {
419 t.Errorf("Table should not exist, but does")
422 if ok := tx.HasTable("bars"); ok {
423 t.Errorf("Table should not exist, but does")
431 err := tx.CreateTable(&Bar{}).Error
434 t.Errorf("Should have been able to create the table, but couldn't: %s", err)
437 if ok := tx.HasTable(&Bar{}); !ok {
438 t.Errorf("The transaction should be able to see the table")
447 err := tx.AutoMigrate(&Bar{}).Error
449 t.Errorf("Should have been able to alter the table, but couldn't")
456 type MultipleIndexes struct {
458 UserID int64 `sql:"unique_index:uix_multipleindexes_user_name,uix_multipleindexes_user_email;index:idx_multipleindexes_user_other"`
459 Name string `sql:"unique_index:uix_multipleindexes_user_name"`
460 Email string `sql:"unique_index:,uix_multipleindexes_user_email"`
461 Other string `sql:"index:,idx_multipleindexes_user_other"`
464 func TestMultipleIndexes(t *testing.T) {
465 if err := DB.DropTableIfExists(&MultipleIndexes{}).Error; err != nil {
466 fmt.Printf("Got error when try to delete table multiple_indexes, %+v\n", err)
469 DB.AutoMigrate(&MultipleIndexes{})
470 if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
471 t.Errorf("Auto Migrate should not raise any error")
474 DB.Save(&MultipleIndexes{UserID: 1, Name: "jinzhu", Email: "jinzhu@example.org", Other: "foo"})
476 scope := DB.NewScope(&MultipleIndexes{})
477 if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_name") {
478 t.Errorf("Failed to create index")
481 if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_email") {
482 t.Errorf("Failed to create index")
485 if !scope.Dialect().HasIndex(scope.TableName(), "uix_multiple_indexes_email") {
486 t.Errorf("Failed to create index")
489 if !scope.Dialect().HasIndex(scope.TableName(), "idx_multipleindexes_user_other") {
490 t.Errorf("Failed to create index")
493 if !scope.Dialect().HasIndex(scope.TableName(), "idx_multiple_indexes_other") {
494 t.Errorf("Failed to create index")
497 var mutipleIndexes MultipleIndexes
498 DB.First(&mutipleIndexes, "name = ?", "jinzhu")
499 if mutipleIndexes.Email != "jinzhu@example.org" || mutipleIndexes.Name != "jinzhu" {
500 t.Error("MutipleIndexes should be saved and fetched correctly")
503 // Check unique constraints
504 if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
505 t.Error("MultipleIndexes unique index failed")
508 if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "foo@example.org", Other: "foo"}).Error; err != nil {
509 t.Error("MultipleIndexes unique index failed")
512 if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
513 t.Error("MultipleIndexes unique index failed")
516 if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "foo2@example.org", Other: "foo"}).Error; err != nil {
517 t.Error("MultipleIndexes unique index failed")
521 func TestModifyColumnType(t *testing.T) {
522 if dialect := os.Getenv("GORM_DIALECT"); dialect != "postgres" && dialect != "mysql" && dialect != "mssql" {
523 t.Skip("Skipping this because only postgres, mysql and mssql support altering a column type")
526 type ModifyColumnType struct {
528 Name1 string `gorm:"length:100"`
529 Name2 string `gorm:"length:200"`
531 DB.DropTable(&ModifyColumnType{})
532 DB.CreateTable(&ModifyColumnType{})
534 name2Field, _ := DB.NewScope(&ModifyColumnType{}).FieldByName("Name2")
535 name2Type := DB.Dialect().DataTypeOf(name2Field.StructField)
537 if err := DB.Model(&ModifyColumnType{}).ModifyColumn("name1", name2Type).Error; err != nil {
538 t.Errorf("No error should happen when ModifyColumn, but got %v", err)