# GORM Notes ## Start * ### Install ```bash go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite ``` * ### Models ```go type User struct { ID uint // Standard field for the primary key Name string // A regular string field Email *string // A pointer to a string, allowing for null values Age uint8 // An unsigned 8-bit integer Birthday *time.Time // A pointer to time.Time, can be null MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields CreatedAt time.Time // Automatically managed by GORM for creation time UpdatedAt time.Time // Automatically managed by GORM for update time } ``` * *string:pointers to types indicate nullable fields. * CreatedAt and UpdatedAt are special fields that GORM automatically populates with the current time when a record is created or updated. #### gorm.model * GORM provides a predefined struct named gorm.Model. * Embed gorm.Model directly in your structs to include these fields automatically. ```go // gorm.Model definition type Model struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` } ``` #### Embedded Struct ```go type User struct { gorm.Model Name string } // equals type User struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Name string } ``` * ### Database - MySQL ```go import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) } ``` ```go import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) } ``` ## CRUD Interface ### Create * Batch Insert ```go var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}} // batch size 100 db.CreateInBatches(users, 100) ``` * Create Hooks > GORM allows user defined hooks to be implemented for BeforeSave, BeforeCreate, AfterSave, AfterCreate. ```go func (u *User) BeforeCreate(tx *gorm.DB) (err error) { u.UUID = uuid.New() if u.Role == "admin" { return errors.New("invalid role") } return } ``` * Upsert/On Conflict ```go import "gorm.io/gorm/clause" // Update columns to default value on `id` conflict db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}), }).Create(&users) // MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL // Update all columns to new value on conflict except primary keys and those columns having default values from sql func db.Clauses(clause.OnConflict{ UpdateAll: true, }).Create(&users) // INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...; // INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; ``` ### Raw SQL & SQL Builder * #### Clauses >GORM uses SQL builder generates SQL internally, for each operation, GORM creates a gorm.Statement object, all GORM APIs add/change Clause for the Statement, at last, GORM generated SQL based on those clauses. ```go var limit = 1 clause.Select{Columns: []clause.Column{{Name: "*"}}} clause.From{Tables: []clause.Table{{Name: clause.CurrentTable}}} clause.Limit{Limit: &limit} clause.OrderBy{Columns: []clause.OrderByColumn{ { Column: clause.Column{ Table: clause.CurrentTable, Name: clause.PrimaryKey, }, }, }} ``` ```go Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR") //SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 ```