OSDN Git Service

feat: init cross_tx keepers (#146)
[bytom/vapor.git] / vendor / github.com / jinzhu / gorm / migration_test.go
1 package gorm_test
2
3 import (
4         "database/sql"
5         "database/sql/driver"
6         "errors"
7         "fmt"
8         "os"
9         "reflect"
10         "strconv"
11         "testing"
12         "time"
13
14         "github.com/jinzhu/gorm"
15 )
16
17 type User struct {
18         Id                int64
19         Age               int64
20         UserNum           Num
21         Name              string `sql:"size:255"`
22         Email             string
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
31         CreditCard        CreditCard
32         Latitude          float64
33         Languages         []Language `gorm:"many2many:user_languages;"`
34         CompanyID         *int
35         Company           Company
36         Role              Role
37         Password          EncryptedData
38         PasswordHash      []byte
39         IgnoreMe          int64                 `sql:"-"`
40         IgnoreStringSlice []string              `sql:"-"`
41         Ignored           struct{ Name string } `sql:"-"`
42         IgnoredPointer    *User                 `sql:"-"`
43 }
44
45 type NotSoLongTableName struct {
46         Id                int64
47         ReallyLongThingID int64
48         ReallyLongThing   ReallyLongTableNameToTestMySQLNameLengthLimit
49 }
50
51 type ReallyLongTableNameToTestMySQLNameLengthLimit struct {
52         Id int64
53 }
54
55 type ReallyLongThingThatReferencesShort struct {
56         Id      int64
57         ShortID int64
58         Short   Short
59 }
60
61 type Short struct {
62         Id int64
63 }
64
65 type CreditCard struct {
66         ID        int8
67         Number    string
68         UserId    sql.NullInt64
69         CreatedAt time.Time `sql:"not null"`
70         UpdatedAt time.Time
71         DeletedAt *time.Time `sql:"column:deleted_time"`
72 }
73
74 type Email struct {
75         Id        int16
76         UserId    int
77         Email     string `sql:"type:varchar(100);"`
78         CreatedAt time.Time
79         UpdatedAt time.Time
80 }
81
82 type Address struct {
83         ID        int
84         Address1  string
85         Address2  string
86         Post      string
87         CreatedAt time.Time
88         UpdatedAt time.Time
89         DeletedAt *time.Time
90 }
91
92 type Language struct {
93         gorm.Model
94         Name  string
95         Users []User `gorm:"many2many:user_languages;"`
96 }
97
98 type Product struct {
99         Id                    int64
100         Code                  string
101         Price                 int64
102         CreatedAt             time.Time
103         UpdatedAt             time.Time
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
113 }
114
115 type Company struct {
116         Id    int64
117         Name  string
118         Owner *User `sql:"-"`
119 }
120
121 type Place struct {
122         Id             int64
123         PlaceAddressID int
124         PlaceAddress   *Address `gorm:"save_associations:false"`
125         OwnerAddressID int
126         OwnerAddress   *Address `gorm:"save_associations:true"`
127 }
128
129 type EncryptedData []byte
130
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")
135                 }
136
137                 *data = b[3:]
138                 return nil
139         }
140
141         return errors.New("Bytes expected")
142 }
143
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'")
148         }
149
150         //prepend asterisks
151         return append([]byte("***"), data...), nil
152 }
153
154 type Role struct {
155         Name string `gorm:"size:256"`
156 }
157
158 func (role *Role) Scan(value interface{}) error {
159         if b, ok := value.([]uint8); ok {
160                 role.Name = string(b)
161         } else {
162                 role.Name = value.(string)
163         }
164         return nil
165 }
166
167 func (role Role) Value() (driver.Value, error) {
168         return role.Name, nil
169 }
170
171 func (role Role) IsAdmin() bool {
172         return role.Name == "admin"
173 }
174
175 type Num int64
176
177 func (i *Num) Scan(src interface{}) error {
178         switch s := src.(type) {
179         case []byte:
180                 n, _ := strconv.Atoi(string(s))
181                 *i = Num(n)
182         case int64:
183                 *i = Num(s)
184         default:
185                 return errors.New("Cannot scan NamedInt from " + reflect.ValueOf(src).String())
186         }
187         return nil
188 }
189
190 type Animal struct {
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
196         CreatedAt  time.Time
197         UpdatedAt  time.Time
198 }
199
200 type JoinTable struct {
201         From uint64
202         To   uint64
203         Time time.Time `sql:"default: null"`
204 }
205
206 type Post struct {
207         Id             int64
208         CategoryId     sql.NullInt64
209         MainCategoryId int64
210         Title          string
211         Body           string
212         Comments       []*Comment
213         Category       Category
214         MainCategory   Category
215 }
216
217 type Category struct {
218         gorm.Model
219         Name string
220
221         Categories []Category
222         CategoryID *uint
223 }
224
225 type Comment struct {
226         gorm.Model
227         PostId  int64
228         Content string
229         Post    Post
230 }
231
232 // Scanner
233 type NullValue struct {
234         Id      int64
235         Name    sql.NullString  `sql:"not null"`
236         Gender  *sql.NullString `sql:"not null"`
237         Age     sql.NullInt64
238         Male    sql.NullBool
239         Height  sql.NullFloat64
240         AddedAt NullTime
241 }
242
243 type NullTime struct {
244         Time  time.Time
245         Valid bool
246 }
247
248 func (nt *NullTime) Scan(value interface{}) error {
249         if value == nil {
250                 nt.Valid = false
251                 return nil
252         }
253         nt.Time, nt.Valid = value.(time.Time), true
254         return nil
255 }
256
257 func (nt NullTime) Value() (driver.Value, error) {
258         if !nt.Valid {
259                 return nil, nil
260         }
261         return nt.Time, nil
262 }
263
264 func getPreparedUser(name string, role string) *User {
265         var company Company
266         DB.Where(Company{Name: role}).FirstOrCreate(&company)
267
268         return &User{
269                 Name:            name,
270                 Age:             20,
271                 Role:            Role{role},
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)},
275                 Emails: []Email{
276                         {Email: fmt.Sprintf("user_%v@example1.com", name)}, {Email: fmt.Sprintf("user_%v@example2.com", name)},
277                 },
278                 Company: company,
279                 Languages: []Language{
280                         {Name: fmt.Sprintf("lang_1_%v", name)},
281                         {Name: fmt.Sprintf("lang_2_%v", name)},
282                 },
283         }
284 }
285
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)
289         }
290
291         for _, table := range []string{"animals", "user_languages"} {
292                 DB.Exec(fmt.Sprintf("drop table %v;", table))
293         }
294
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 {
297                 DB.DropTable(value)
298         }
299         if err := DB.AutoMigrate(values...).Error; err != nil {
300                 panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
301         }
302 }
303
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)
307         }
308
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")
312         }
313
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)
316         }
317
318         if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
319                 t.Errorf("Email's index idx_email_email should be deleted")
320         }
321
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)
324         }
325
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")
328         }
329
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)
332         }
333
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")
336         }
337
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)
340         }
341
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")
344         }
345
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")
348         }
349
350         var user = User{Name: "sample_user"}
351         DB.Save(&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")
354         }
355
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")
358         }
359
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)
362         }
363
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")
366         }
367
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")
370         }
371 }
372
373 type EmailWithIdx struct {
374         Id           int64
375         UserId       int64
376         Email        string     `sql:"index:idx_email_agent"`
377         UserAgent    string     `sql:"index:idx_email_agent"`
378         RegisteredAt *time.Time `sql:"unique_index"`
379         CreatedAt    time.Time
380         UpdatedAt    time.Time
381 }
382
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")
388         }
389
390         now := time.Now()
391         DB.Save(&EmailWithIdx{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: &now})
392
393         scope := DB.NewScope(&EmailWithIdx{})
394         if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_agent") {
395                 t.Errorf("Failed to create index")
396         }
397
398         if !scope.Dialect().HasIndex(scope.TableName(), "uix_email_with_idxes_registered_at") {
399                 t.Errorf("Failed to create index")
400         }
401
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")
406         }
407 }
408
409 func TestCreateAndAutomigrateTransaction(t *testing.T) {
410         tx := DB.Begin()
411
412         func() {
413                 type Bar struct {
414                         ID uint
415                 }
416                 DB.DropTableIfExists(&Bar{})
417
418                 if ok := DB.HasTable("bars"); ok {
419                         t.Errorf("Table should not exist, but does")
420                 }
421
422                 if ok := tx.HasTable("bars"); ok {
423                         t.Errorf("Table should not exist, but does")
424                 }
425         }()
426
427         func() {
428                 type Bar struct {
429                         Name string
430                 }
431                 err := tx.CreateTable(&Bar{}).Error
432
433                 if err != nil {
434                         t.Errorf("Should have been able to create the table, but couldn't: %s", err)
435                 }
436
437                 if ok := tx.HasTable(&Bar{}); !ok {
438                         t.Errorf("The transaction should be able to see the table")
439                 }
440         }()
441
442         func() {
443                 type Bar struct {
444                         Stuff string
445                 }
446
447                 err := tx.AutoMigrate(&Bar{}).Error
448                 if err != nil {
449                         t.Errorf("Should have been able to alter the table, but couldn't")
450                 }
451         }()
452
453         tx.Rollback()
454 }
455
456 type MultipleIndexes struct {
457         ID     int64
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"`
462 }
463
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)
467         }
468
469         DB.AutoMigrate(&MultipleIndexes{})
470         if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
471                 t.Errorf("Auto Migrate should not raise any error")
472         }
473
474         DB.Save(&MultipleIndexes{UserID: 1, Name: "jinzhu", Email: "jinzhu@example.org", Other: "foo"})
475
476         scope := DB.NewScope(&MultipleIndexes{})
477         if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_name") {
478                 t.Errorf("Failed to create index")
479         }
480
481         if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_email") {
482                 t.Errorf("Failed to create index")
483         }
484
485         if !scope.Dialect().HasIndex(scope.TableName(), "uix_multiple_indexes_email") {
486                 t.Errorf("Failed to create index")
487         }
488
489         if !scope.Dialect().HasIndex(scope.TableName(), "idx_multipleindexes_user_other") {
490                 t.Errorf("Failed to create index")
491         }
492
493         if !scope.Dialect().HasIndex(scope.TableName(), "idx_multiple_indexes_other") {
494                 t.Errorf("Failed to create index")
495         }
496
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")
501         }
502
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")
506         }
507
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")
510         }
511
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")
514         }
515
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")
518         }
519 }
520
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")
524         }
525
526         type ModifyColumnType struct {
527                 gorm.Model
528                 Name1 string `gorm:"length:100"`
529                 Name2 string `gorm:"length:200"`
530         }
531         DB.DropTable(&ModifyColumnType{})
532         DB.CreateTable(&ModifyColumnType{})
533
534         name2Field, _ := DB.NewScope(&ModifyColumnType{}).FieldByName("Name2")
535         name2Type := DB.Dialect().DataTypeOf(name2Field.StructField)
536
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)
539         }
540 }