diff --git a/README.md b/README.md index 69b6bd16..8d995853 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ type User struct { ID int Birthday time.Time Age int - Name string `sql:"size:255"` + Name string `sql:"size:255"` // Default size for string is 255, you could reset it with this tag CreatedAt time.Time UpdatedAt time.Time DeletedAt time.Time @@ -50,9 +50,9 @@ type User struct { } type Email struct { - ID int - UserID int // Foreign key for User (belongs to) - Email string `sql:"type:varchar(100);"` // Set field's type + ID int + UserID int `sql:"index"` // Foreign key (belongs to), tag `index` will create index for this field when using AutoMigrate + Email string `sql:"type:varchar(100);unique_index"` // Set field's sql type, tag `unique_index` will create unique index Subscribed bool } @@ -65,7 +65,8 @@ type Address struct { type Language struct { ID int - Name string + Name string `sql:"index:idx_name_code"` // Create index with name, and will create combined index if find other fields defined same name + Code string `sql:"index:idx_name_code"` // `unique_index` also works } ``` @@ -74,13 +75,7 @@ type Language struct { * Table name is the plural of struct name's snake case, you can disable pluralization with `db.SingularTable(true)`, or [Specifying The Table Name For A Struct Permanently With TableName](#specifying-the-table-name-for-a-struct-permanently-with-tablename) ```go -// E.g finding an existing User -var user User -// Gorm will know to use table "users" ("user" if pluralisation has been disabled) for all operations. -db.First(&user) - -// creating a new User -db.Save(&User{Name: "xxx"}) // table "users" +type User struct{} // struct User's database table name is "users" by default, will be "user" if you disabled pluralisation ``` * Column name is the snake case of field's name @@ -92,7 +87,6 @@ db.Save(&User{Name: "xxx"}) // table "users" ## Initialize Database ```go - import ( "github.com/jinzhu/gorm" _ "github.com/lib/pq" @@ -101,7 +95,7 @@ import ( ) db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable") -// db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True") +// db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local") // db, err := gorm.Open("sqlite3", "/tmp/gorm.db") // You can also use an existing database connection handle @@ -129,38 +123,13 @@ db.CreateTable(&User{}) // Drop table db.DropTable(&User{}) -// Drop table if exists -db.DropTableIfExists(&User{}) - // Automating Migration db.AutoMigrate(&User{}) db.AutoMigrate(&User{}, &Product{}, &Order{}) - // Feel free to change your struct, AutoMigrate will keep your database up-to-date. -// Fyi, AutoMigrate will only *add new columns*, it won't update column's type or delete unused columns, to make sure your data is safe. +// AutoMigrate will ONLY add *new columns* and *new indexes*, +// WON'T update current column's type or delete unused columns, to protect your data. // If the table is not existing, AutoMigrate will create the table automatically. - -// Add index -db.Model(&User{}).AddIndex("idx_user_name", "name") - -// Add foreign key -// 1st param : foreignkey field -// 2nd param : destination table(id) -// 3rd param : ONDELETE -// 4th param : ONUPDATE -db.Model(&User{}).AddForeignKey("user_id", "destination_table(id)", "CASCADE", "RESTRICT") - -// Multiple column index -db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age") - -// Add unique index -db.Model(&User{}).AddUniqueIndex("idx_user_name", "name") - -// Multiple column unique index -db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age") - -// Remove index -db.Model(&User{}).RemoveIndex("idx_user_name") ``` # Basic CRUD @@ -170,18 +139,13 @@ db.Model(&User{}).RemoveIndex("idx_user_name") ```go user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} -// returns true if record hasn’t been saved (primary key `Id` is blank) -db.NewRecord(user) // => true +db.NewRecord(user) // => returns `true` if primary key is blank db.Create(&user) -// will return false after `user` created -db.NewRecord(user) // => false +db.NewRecord(user) // => return `false` after `user` created -// You could use `Save` to create record also if its primary key is null -db.Save(&user) - -// Associations will be saved automatically when insert the record +// Associations will be inserted automatically when save the record user := User{ Name: "jinzhu", BillingAddress: Address{Address1: "Billing Address - Address 1"}, @@ -204,7 +168,7 @@ db.Create(&user) //// COMMIT; ``` -Refer [Associations](#associations) for how to work with associations +Refer [Associations](#associations) for more details ## Query @@ -1095,6 +1059,32 @@ INSERT INTO animals("age") values('99'); The same thing occurs in update statements. +## Database Indexes & Foreign Key + +```go +// Add foreign key +// 1st param : foreignkey field +// 2nd param : destination table(id) +// 3rd param : ONDELETE +// 4th param : ONUPDATE +db.Model(&User{}).AddForeignKey("user_id", "destination_table(id)", "CASCADE", "RESTRICT") + +// Add index +db.Model(&User{}).AddIndex("idx_user_name", "name") + +// Multiple column index +db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age") + +// Add unique index +db.Model(&User{}).AddUniqueIndex("idx_user_name", "name") + +// Multiple column unique index +db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age") + +// Remove index +db.Model(&User{}).RemoveIndex("idx_user_name") +``` + ## More examples with query chain ```go diff --git a/migration_test.go b/migration_test.go index 951b79c6..74c8b94f 100644 --- a/migration_test.go +++ b/migration_test.go @@ -87,9 +87,9 @@ func TestIndexes(t *testing.T) { type BigEmail struct { Id int64 UserId int64 - Email string - UserAgent string - RegisteredAt time.Time + 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 } @@ -106,6 +106,15 @@ func TestAutoMigration(t *testing.T) { DB.Save(&BigEmail{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: time.Now()}) + scope := DB.NewScope(&BigEmail{}) + if !scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_agent") { + t.Errorf("Failed to create index") + } + + if !scope.Dialect().HasIndex(scope, scope.TableName(), "uix_emails_registered_at") { + t.Errorf("Failed to create index") + } + var bigemail BigEmail DB.First(&bigemail, "user_agent = ?", "pc") if bigemail.Email != "jinzhu@example.org" || bigemail.UserAgent != "pc" || bigemail.RegisteredAt.IsZero() { diff --git a/scope_private.go b/scope_private.go index 308b4cb3..40aeaca8 100644 --- a/scope_private.go +++ b/scope_private.go @@ -532,5 +532,43 @@ func (scope *Scope) autoMigrate() *Scope { scope.createJoinTable(field) } } + + scope.autoIndex() + return scope +} + +func (scope *Scope) autoIndex() *Scope { + var indexes = map[string][]string{} + var uniqueIndexes = map[string][]string{} + + for _, field := range scope.GetStructFields() { + sqlSettings := parseTagSetting(field.Tag.Get("sql")) + if name, ok := sqlSettings["INDEX"]; ok { + if name == "INDEX" { + name = fmt.Sprintf("idx_%v_%v", scope.TableName(), field.DBName) + } + indexes[name] = append(indexes[name], field.DBName) + } + + if name, ok := sqlSettings["UNIQUE_INDEX"]; ok { + if name == "UNIQUE_INDEX" { + name = fmt.Sprintf("uix_%v_%v", scope.TableName(), field.DBName) + } + uniqueIndexes[name] = append(indexes[name], field.DBName) + } + } + + for name, columns := range indexes { + if !scope.Dialect().HasIndex(scope, scope.TableName(), name) { + scope.addIndex(false, name, columns...) + } + } + + for name, columns := range uniqueIndexes { + if !scope.Dialect().HasIndex(scope, scope.TableName(), name) { + scope.addIndex(true, name, columns...) + } + } + return scope }