migration_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. package gorm_test
  2. import (
  3. "database/sql"
  4. "database/sql/driver"
  5. "errors"
  6. "fmt"
  7. "os"
  8. "reflect"
  9. "strconv"
  10. "testing"
  11. "time"
  12. "github.com/jinzhu/gorm"
  13. )
  14. type User struct {
  15. Id int64
  16. Age int64
  17. UserNum Num
  18. Name string `sql:"size:255"`
  19. Email string
  20. Birthday *time.Time // Time
  21. CreatedAt time.Time // CreatedAt: Time of record is created, will be insert automatically
  22. UpdatedAt time.Time // UpdatedAt: Time of record is updated, will be updated automatically
  23. Emails []Email // Embedded structs
  24. BillingAddress Address // Embedded struct
  25. BillingAddressID sql.NullInt64 // Embedded struct's foreign key
  26. ShippingAddress Address // Embedded struct
  27. ShippingAddressId int64 // Embedded struct's foreign key
  28. CreditCard CreditCard
  29. Latitude float64
  30. Languages []Language `gorm:"many2many:user_languages;"`
  31. CompanyID *int
  32. Company Company
  33. Role Role
  34. Password EncryptedData
  35. PasswordHash []byte
  36. IgnoreMe int64 `sql:"-"`
  37. IgnoreStringSlice []string `sql:"-"`
  38. Ignored struct{ Name string } `sql:"-"`
  39. IgnoredPointer *User `sql:"-"`
  40. }
  41. type NotSoLongTableName struct {
  42. Id int64
  43. ReallyLongThingID int64
  44. ReallyLongThing ReallyLongTableNameToTestMySQLNameLengthLimit
  45. }
  46. type ReallyLongTableNameToTestMySQLNameLengthLimit struct {
  47. Id int64
  48. }
  49. type ReallyLongThingThatReferencesShort struct {
  50. Id int64
  51. ShortID int64
  52. Short Short
  53. }
  54. type Short struct {
  55. Id int64
  56. }
  57. type CreditCard struct {
  58. ID int8
  59. Number string
  60. UserId sql.NullInt64
  61. CreatedAt time.Time `sql:"not null"`
  62. UpdatedAt time.Time
  63. DeletedAt *time.Time `sql:"column:deleted_time"`
  64. }
  65. type Email struct {
  66. Id int16
  67. UserId int
  68. Email string `sql:"type:varchar(100);"`
  69. CreatedAt time.Time
  70. UpdatedAt time.Time
  71. }
  72. type Address struct {
  73. ID int
  74. Address1 string
  75. Address2 string
  76. Post string
  77. CreatedAt time.Time
  78. UpdatedAt time.Time
  79. DeletedAt *time.Time
  80. }
  81. type Language struct {
  82. gorm.Model
  83. Name string
  84. Users []User `gorm:"many2many:user_languages;"`
  85. }
  86. type Product struct {
  87. Id int64
  88. Code string
  89. Price int64
  90. CreatedAt time.Time
  91. UpdatedAt time.Time
  92. AfterFindCallTimes int64
  93. BeforeCreateCallTimes int64
  94. AfterCreateCallTimes int64
  95. BeforeUpdateCallTimes int64
  96. AfterUpdateCallTimes int64
  97. BeforeSaveCallTimes int64
  98. AfterSaveCallTimes int64
  99. BeforeDeleteCallTimes int64
  100. AfterDeleteCallTimes int64
  101. }
  102. type Company struct {
  103. Id int64
  104. Name string
  105. Owner *User `sql:"-"`
  106. }
  107. type Place struct {
  108. Id int64
  109. PlaceAddressID int
  110. PlaceAddress *Address `gorm:"save_associations:false"`
  111. OwnerAddressID int
  112. OwnerAddress *Address `gorm:"save_associations:true"`
  113. }
  114. type EncryptedData []byte
  115. func (data *EncryptedData) Scan(value interface{}) error {
  116. if b, ok := value.([]byte); ok {
  117. if len(b) < 3 || b[0] != '*' || b[1] != '*' || b[2] != '*' {
  118. return errors.New("Too short")
  119. }
  120. *data = b[3:]
  121. return nil
  122. }
  123. return errors.New("Bytes expected")
  124. }
  125. func (data EncryptedData) Value() (driver.Value, error) {
  126. if len(data) > 0 && data[0] == 'x' {
  127. //needed to test failures
  128. return nil, errors.New("Should not start with 'x'")
  129. }
  130. //prepend asterisks
  131. return append([]byte("***"), data...), nil
  132. }
  133. type Role struct {
  134. Name string `gorm:"size:256"`
  135. }
  136. func (role *Role) Scan(value interface{}) error {
  137. if b, ok := value.([]uint8); ok {
  138. role.Name = string(b)
  139. } else {
  140. role.Name = value.(string)
  141. }
  142. return nil
  143. }
  144. func (role Role) Value() (driver.Value, error) {
  145. return role.Name, nil
  146. }
  147. func (role Role) IsAdmin() bool {
  148. return role.Name == "admin"
  149. }
  150. type Num int64
  151. func (i *Num) Scan(src interface{}) error {
  152. switch s := src.(type) {
  153. case []byte:
  154. n, _ := strconv.Atoi(string(s))
  155. *i = Num(n)
  156. case int64:
  157. *i = Num(s)
  158. default:
  159. return errors.New("Cannot scan NamedInt from " + reflect.ValueOf(src).String())
  160. }
  161. return nil
  162. }
  163. type Animal struct {
  164. Counter uint64 `gorm:"primary_key:yes"`
  165. Name string `sql:"DEFAULT:'galeone'"`
  166. From string //test reserved sql keyword as field name
  167. Age time.Time `sql:"DEFAULT:current_timestamp"`
  168. unexported string // unexported value
  169. CreatedAt time.Time
  170. UpdatedAt time.Time
  171. }
  172. type JoinTable struct {
  173. From uint64
  174. To uint64
  175. Time time.Time `sql:"default: null"`
  176. }
  177. type Post struct {
  178. Id int64
  179. CategoryId sql.NullInt64
  180. MainCategoryId int64
  181. Title string
  182. Body string
  183. Comments []*Comment
  184. Category Category
  185. MainCategory Category
  186. }
  187. type Category struct {
  188. gorm.Model
  189. Name string
  190. Categories []Category
  191. CategoryID *uint
  192. }
  193. type Comment struct {
  194. gorm.Model
  195. PostId int64
  196. Content string
  197. Post Post
  198. }
  199. // Scanner
  200. type NullValue struct {
  201. Id int64
  202. Name sql.NullString `sql:"not null"`
  203. Gender *sql.NullString `sql:"not null"`
  204. Age sql.NullInt64
  205. Male sql.NullBool
  206. Height sql.NullFloat64
  207. AddedAt NullTime
  208. }
  209. type NullTime struct {
  210. Time time.Time
  211. Valid bool
  212. }
  213. func (nt *NullTime) Scan(value interface{}) error {
  214. if value == nil {
  215. nt.Valid = false
  216. return nil
  217. }
  218. nt.Time, nt.Valid = value.(time.Time), true
  219. return nil
  220. }
  221. func (nt NullTime) Value() (driver.Value, error) {
  222. if !nt.Valid {
  223. return nil, nil
  224. }
  225. return nt.Time, nil
  226. }
  227. func getPreparedUser(name string, role string) *User {
  228. var company Company
  229. DB.Where(Company{Name: role}).FirstOrCreate(&company)
  230. return &User{
  231. Name: name,
  232. Age: 20,
  233. Role: Role{role},
  234. BillingAddress: Address{Address1: fmt.Sprintf("Billing Address %v", name)},
  235. ShippingAddress: Address{Address1: fmt.Sprintf("Shipping Address %v", name)},
  236. CreditCard: CreditCard{Number: fmt.Sprintf("123456%v", name)},
  237. Emails: []Email{
  238. {Email: fmt.Sprintf("user_%v@example1.com", name)}, {Email: fmt.Sprintf("user_%v@example2.com", name)},
  239. },
  240. Company: company,
  241. Languages: []Language{
  242. {Name: fmt.Sprintf("lang_1_%v", name)},
  243. {Name: fmt.Sprintf("lang_2_%v", name)},
  244. },
  245. }
  246. }
  247. func runMigration() {
  248. if err := DB.DropTableIfExists(&User{}).Error; err != nil {
  249. fmt.Printf("Got error when try to delete table users, %+v\n", err)
  250. }
  251. for _, table := range []string{"animals", "user_languages"} {
  252. DB.Exec(fmt.Sprintf("drop table %v;", table))
  253. }
  254. 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{}}
  255. for _, value := range values {
  256. DB.DropTable(value)
  257. }
  258. if err := DB.AutoMigrate(values...).Error; err != nil {
  259. panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
  260. }
  261. }
  262. func TestIndexes(t *testing.T) {
  263. if err := DB.Model(&Email{}).AddIndex("idx_email_email", "email").Error; err != nil {
  264. t.Errorf("Got error when tried to create index: %+v", err)
  265. }
  266. scope := DB.NewScope(&Email{})
  267. if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
  268. t.Errorf("Email should have index idx_email_email")
  269. }
  270. if err := DB.Model(&Email{}).RemoveIndex("idx_email_email").Error; err != nil {
  271. t.Errorf("Got error when tried to remove index: %+v", err)
  272. }
  273. if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email") {
  274. t.Errorf("Email's index idx_email_email should be deleted")
  275. }
  276. if err := DB.Model(&Email{}).AddIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
  277. t.Errorf("Got error when tried to create index: %+v", err)
  278. }
  279. if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
  280. t.Errorf("Email should have index idx_email_email_and_user_id")
  281. }
  282. if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
  283. t.Errorf("Got error when tried to remove index: %+v", err)
  284. }
  285. if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
  286. t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
  287. }
  288. if err := DB.Model(&Email{}).AddUniqueIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
  289. t.Errorf("Got error when tried to create index: %+v", err)
  290. }
  291. if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
  292. t.Errorf("Email should have index idx_email_email_and_user_id")
  293. }
  294. if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.comiii"}, {Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error == nil {
  295. t.Errorf("Should get to create duplicate record when having unique index")
  296. }
  297. var user = User{Name: "sample_user"}
  298. DB.Save(&user)
  299. if DB.Model(&user).Association("Emails").Append(Email{Email: "not-1duplicated@gmail.com"}, Email{Email: "not-duplicated2@gmail.com"}).Error != nil {
  300. t.Errorf("Should get no error when append two emails for user")
  301. }
  302. if DB.Model(&user).Association("Emails").Append(Email{Email: "duplicated@gmail.com"}, Email{Email: "duplicated@gmail.com"}).Error == nil {
  303. t.Errorf("Should get no duplicated email error when insert duplicated emails for a user")
  304. }
  305. if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
  306. t.Errorf("Got error when tried to remove index: %+v", err)
  307. }
  308. if scope.Dialect().HasIndex(scope.TableName(), "idx_email_email_and_user_id") {
  309. t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
  310. }
  311. if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error != nil {
  312. t.Errorf("Should be able to create duplicated emails after remove unique index")
  313. }
  314. }
  315. type EmailWithIdx struct {
  316. Id int64
  317. UserId int64
  318. Email string `sql:"index:idx_email_agent"`
  319. UserAgent string `sql:"index:idx_email_agent"`
  320. RegisteredAt *time.Time `sql:"unique_index"`
  321. CreatedAt time.Time
  322. UpdatedAt time.Time
  323. }
  324. func TestAutoMigration(t *testing.T) {
  325. DB.AutoMigrate(&Address{})
  326. DB.DropTable(&EmailWithIdx{})
  327. if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
  328. t.Errorf("Auto Migrate should not raise any error")
  329. }
  330. now := time.Now()
  331. DB.Save(&EmailWithIdx{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: &now})
  332. scope := DB.NewScope(&EmailWithIdx{})
  333. if !scope.Dialect().HasIndex(scope.TableName(), "idx_email_agent") {
  334. t.Errorf("Failed to create index")
  335. }
  336. if !scope.Dialect().HasIndex(scope.TableName(), "uix_email_with_idxes_registered_at") {
  337. t.Errorf("Failed to create index")
  338. }
  339. var bigemail EmailWithIdx
  340. DB.First(&bigemail, "user_agent = ?", "pc")
  341. if bigemail.Email != "jinzhu@example.org" || bigemail.UserAgent != "pc" || bigemail.RegisteredAt.IsZero() {
  342. t.Error("Big Emails should be saved and fetched correctly")
  343. }
  344. }
  345. func TestCreateAndAutomigrateTransaction(t *testing.T) {
  346. tx := DB.Begin()
  347. func() {
  348. type Bar struct {
  349. ID uint
  350. }
  351. DB.DropTableIfExists(&Bar{})
  352. if ok := DB.HasTable("bars"); ok {
  353. t.Errorf("Table should not exist, but does")
  354. }
  355. if ok := tx.HasTable("bars"); ok {
  356. t.Errorf("Table should not exist, but does")
  357. }
  358. }()
  359. func() {
  360. type Bar struct {
  361. Name string
  362. }
  363. err := tx.CreateTable(&Bar{}).Error
  364. if err != nil {
  365. t.Errorf("Should have been able to create the table, but couldn't: %s", err)
  366. }
  367. if ok := tx.HasTable(&Bar{}); !ok {
  368. t.Errorf("The transaction should be able to see the table")
  369. }
  370. }()
  371. func() {
  372. type Bar struct {
  373. Stuff string
  374. }
  375. err := tx.AutoMigrate(&Bar{}).Error
  376. if err != nil {
  377. t.Errorf("Should have been able to alter the table, but couldn't")
  378. }
  379. }()
  380. tx.Rollback()
  381. }
  382. type MultipleIndexes struct {
  383. ID int64
  384. UserID int64 `sql:"unique_index:uix_multipleindexes_user_name,uix_multipleindexes_user_email;index:idx_multipleindexes_user_other"`
  385. Name string `sql:"unique_index:uix_multipleindexes_user_name"`
  386. Email string `sql:"unique_index:,uix_multipleindexes_user_email"`
  387. Other string `sql:"index:,idx_multipleindexes_user_other"`
  388. }
  389. func TestMultipleIndexes(t *testing.T) {
  390. if err := DB.DropTableIfExists(&MultipleIndexes{}).Error; err != nil {
  391. fmt.Printf("Got error when try to delete table multiple_indexes, %+v\n", err)
  392. }
  393. DB.AutoMigrate(&MultipleIndexes{})
  394. if err := DB.AutoMigrate(&EmailWithIdx{}).Error; err != nil {
  395. t.Errorf("Auto Migrate should not raise any error")
  396. }
  397. DB.Save(&MultipleIndexes{UserID: 1, Name: "jinzhu", Email: "jinzhu@example.org", Other: "foo"})
  398. scope := DB.NewScope(&MultipleIndexes{})
  399. if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_name") {
  400. t.Errorf("Failed to create index")
  401. }
  402. if !scope.Dialect().HasIndex(scope.TableName(), "uix_multipleindexes_user_email") {
  403. t.Errorf("Failed to create index")
  404. }
  405. if !scope.Dialect().HasIndex(scope.TableName(), "uix_multiple_indexes_email") {
  406. t.Errorf("Failed to create index")
  407. }
  408. if !scope.Dialect().HasIndex(scope.TableName(), "idx_multipleindexes_user_other") {
  409. t.Errorf("Failed to create index")
  410. }
  411. if !scope.Dialect().HasIndex(scope.TableName(), "idx_multiple_indexes_other") {
  412. t.Errorf("Failed to create index")
  413. }
  414. var mutipleIndexes MultipleIndexes
  415. DB.First(&mutipleIndexes, "name = ?", "jinzhu")
  416. if mutipleIndexes.Email != "jinzhu@example.org" || mutipleIndexes.Name != "jinzhu" {
  417. t.Error("MutipleIndexes should be saved and fetched correctly")
  418. }
  419. // Check unique constraints
  420. if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
  421. t.Error("MultipleIndexes unique index failed")
  422. }
  423. if err := DB.Save(&MultipleIndexes{UserID: 1, Name: "name1", Email: "foo@example.org", Other: "foo"}).Error; err != nil {
  424. t.Error("MultipleIndexes unique index failed")
  425. }
  426. if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "jinzhu@example.org", Other: "foo"}).Error; err == nil {
  427. t.Error("MultipleIndexes unique index failed")
  428. }
  429. if err := DB.Save(&MultipleIndexes{UserID: 2, Name: "name1", Email: "foo2@example.org", Other: "foo"}).Error; err != nil {
  430. t.Error("MultipleIndexes unique index failed")
  431. }
  432. }
  433. func TestModifyColumnType(t *testing.T) {
  434. if dialect := os.Getenv("GORM_DIALECT"); dialect != "postgres" && dialect != "mysql" && dialect != "mssql" {
  435. t.Skip("Skipping this because only postgres, mysql and mssql support altering a column type")
  436. }
  437. type ModifyColumnType struct {
  438. gorm.Model
  439. Name1 string `gorm:"length:100"`
  440. Name2 string `gorm:"length:200"`
  441. }
  442. DB.DropTable(&ModifyColumnType{})
  443. DB.CreateTable(&ModifyColumnType{})
  444. name2Field, _ := DB.NewScope(&ModifyColumnType{}).FieldByName("Name2")
  445. name2Type := DB.Dialect().DataTypeOf(name2Field.StructField)
  446. if err := DB.Model(&ModifyColumnType{}).ModifyColumn("name1", name2Type).Error; err != nil {
  447. t.Errorf("No error should happen when ModifyColumn, but got %v", err)
  448. }
  449. }
  450. func TestIndexWithPrefixLength(t *testing.T) {
  451. if dialect := os.Getenv("GORM_DIALECT"); dialect != "mysql" {
  452. t.Skip("Skipping this because only mysql support setting an index prefix length")
  453. }
  454. type IndexWithPrefix struct {
  455. gorm.Model
  456. Name string
  457. Description string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
  458. }
  459. type IndexesWithPrefix struct {
  460. gorm.Model
  461. Name string
  462. Description1 string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
  463. Description2 string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
  464. }
  465. type IndexesWithPrefixAndWithoutPrefix struct {
  466. gorm.Model
  467. Name string `gorm:"index:idx_index_with_prefixes_length"`
  468. Description string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
  469. }
  470. tables := []interface{}{&IndexWithPrefix{}, &IndexesWithPrefix{}, &IndexesWithPrefixAndWithoutPrefix{}}
  471. for _, table := range tables {
  472. scope := DB.NewScope(table)
  473. tableName := scope.TableName()
  474. t.Run(fmt.Sprintf("Create index with prefix length: %s", tableName), func(t *testing.T) {
  475. if err := DB.DropTableIfExists(table).Error; err != nil {
  476. t.Errorf("Failed to drop %s table: %v", tableName, err)
  477. }
  478. if err := DB.CreateTable(table).Error; err != nil {
  479. t.Errorf("Failed to create %s table: %v", tableName, err)
  480. }
  481. if !scope.Dialect().HasIndex(tableName, "idx_index_with_prefixes_length") {
  482. t.Errorf("Failed to create %s table index:", tableName)
  483. }
  484. })
  485. }
  486. }