mirror of https://github.com/go-gorm/gorm.git
Update document
This commit is contained in:
parent
15fd62a0d1
commit
ba8b2857ea
|
@ -1,2 +1,3 @@
|
||||||
/_book
|
/_book
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/gitbook
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
# GORM
|
# GORM Guides
|
||||||
|
|
||||||
This is GORM
|
The fantastic ORM library for Golang, aims to be developer friendly.
|
||||||
|
|
||||||
|
[![wercker status](https://app.wercker.com/status/0cb7bb1039e21b74f8274941428e0921/s/master "wercker status")](https://app.wercker.com/project/bykey/0cb7bb1039e21b74f8274941428e0921)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/jinzhu/gorm?status.svg)](https://godoc.org/github.com/jinzhu/gorm)
|
||||||
|
[![Join the chat at https://gitter.im/jinzhu/gorm](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jinzhu/gorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
* Full-Featured ORM (almost)
|
||||||
|
* Chainable API
|
||||||
|
* Auto Migrations
|
||||||
|
* Relations (Has One, Has Many, Belongs To, Many To Many, [Polymorphism](#polymorphism))
|
||||||
|
* Callbacks (Before/After Create/Save/Update/Delete/Find)
|
||||||
|
* Preloading (eager loading)
|
||||||
|
* Transactions
|
||||||
|
* Embed Anonymous Struct
|
||||||
|
* Soft Deletes
|
||||||
|
* Customizable Logger
|
||||||
|
* Iteration Support via [Rows](#row--rows)
|
||||||
|
* Every feature comes with tests
|
||||||
|
* Developer Friendly
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/jinzhu/gorm
|
||||||
|
```
|
||||||
|
|
||||||
|
# Author
|
||||||
|
|
||||||
|
**jinzhu**
|
||||||
|
|
||||||
|
* <http://github.com/jinzhu>
|
||||||
|
* <wosmvp@gmail.com>
|
||||||
|
* <http://twitter.com/zhangjinzhu>
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
https://github.com/jinzhu/gorm/graphs/contributors
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Released under the [MIT License](https://github.com/jinzhu/gorm/blob/master/License).
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Composite Primary Key
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Product struct {
|
||||||
|
ID string `gorm:"primary_key"`
|
||||||
|
LanguageCode string `gorm:"primary_key"`
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Error Handling
|
||||||
|
|
||||||
|
```go
|
||||||
|
query := db.Where("name = ?", "jinzhu").First(&user)
|
||||||
|
query := db.First(&user).Limit(10).Find(&users)
|
||||||
|
// query.Error will return the last happened error
|
||||||
|
|
||||||
|
// So you could do error handing in your application like this:
|
||||||
|
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
|
||||||
|
// error handling...
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordNotFound
|
||||||
|
// If no record found when you query data, gorm will return RecordNotFound error, you could check it like this:
|
||||||
|
db.Where("name = ?", "hello world").First(&User{}).Error == gorm.RecordNotFound
|
||||||
|
// Or use the shortcut method
|
||||||
|
db.Where("name = ?", "hello world").First(&user).RecordNotFound()
|
||||||
|
|
||||||
|
if db.Model(&user).Related(&credit_card).RecordNotFound() {
|
||||||
|
// no credit card found error handling
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logger
|
||||||
|
|
||||||
|
Gorm has built-in logger support
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Enable Logger
|
||||||
|
db.LogMode(true)
|
||||||
|
|
||||||
|
// Diable Logger
|
||||||
|
db.LogMode(false)
|
||||||
|
|
||||||
|
// Debug a single operation
|
||||||
|
db.Debug().Where("name = ?", "jinzhu").First(&User{})
|
||||||
|
```
|
||||||
|
|
||||||
|
![logger](https://raw.github.com/jinzhu/gorm/master/doc/logger.png)
|
||||||
|
|
||||||
|
### Customize Logger
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Refer gorm's default logger for how to: https://github.com/jinzhu/gorm/blob/master/logger.go#files
|
||||||
|
db.SetLogger(gorm.Logger{revel.TRACE})
|
||||||
|
db.SetLogger(log.New(os.Stdout, "\r\n", 0))
|
||||||
|
```
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Query Chain
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles)
|
||||||
|
//// SELECT * FROM articles LIMIT 1; (first_article)
|
||||||
|
//// SELECT count(*) FROM articles; (total_count)
|
||||||
|
//// SELECT * FROM articles LIMIT 10; (first_page_articles)
|
||||||
|
//// SELECT * FROM articles LIMIT 10 OFFSET 10; (second_page_articles)
|
||||||
|
|
||||||
|
|
||||||
|
db.Where("created_at > ?", "2013-10-10").Find(&cancelled_orders, "state = ?", "cancelled").Find(&shipped_orders, "state = ?", "shipped")
|
||||||
|
//// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'cancelled'; (cancelled_orders)
|
||||||
|
//// SELECT * FROM orders WHERE created_at > '2013/10/10' AND state = 'shipped'; (shipped_orders)
|
||||||
|
|
||||||
|
|
||||||
|
// Use variables to keep query chain
|
||||||
|
todays_orders := db.Where("created_at > ?", "2013-10-29")
|
||||||
|
cancelled_orders := todays_orders.Where("state = ?", "cancelled")
|
||||||
|
shipped_orders := todays_orders.Where("state = ?", "shipped")
|
||||||
|
|
||||||
|
|
||||||
|
// Search with shared conditions for different tables
|
||||||
|
db.Where("product_name = ?", "fancy_product").Find(&orders).Find(&shopping_carts)
|
||||||
|
//// SELECT * FROM orders WHERE product_name = 'fancy_product'; (orders)
|
||||||
|
//// SELECT * FROM carts WHERE product_name = 'fancy_product'; (shopping_carts)
|
||||||
|
|
||||||
|
|
||||||
|
// Search with shared conditions from different tables with specified table
|
||||||
|
db.Where("mail_type = ?", "TEXT").Find(&users1).Table("deleted_users").Find(&users2)
|
||||||
|
//// SELECT * FROM users WHERE mail_type = 'TEXT'; (users1)
|
||||||
|
//// SELECT * FROM deleted_users WHERE mail_type = 'TEXT'; (users2)
|
||||||
|
|
||||||
|
|
||||||
|
// FirstOrCreate example
|
||||||
|
db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111"}).FirstOrCreate(&user)
|
||||||
|
//// SELECT * FROM users WHERE email = 'x@example.org';
|
||||||
|
//// INSERT INTO "users" (email,registered_ip) VALUES ("x@example.org", "111.111.111.111") // if record not found
|
||||||
|
```
|
|
@ -0,0 +1,45 @@
|
||||||
|
## Raw SQL
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Exec("DROP TABLE users;")
|
||||||
|
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Row & Rows
|
||||||
|
|
||||||
|
It is even possible to get query result as `*sql.Row` or `*sql.Rows`
|
||||||
|
|
||||||
|
```go
|
||||||
|
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
|
||||||
|
row.Scan(&name, &age)
|
||||||
|
|
||||||
|
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
...
|
||||||
|
rows.Scan(&name, &age, &email)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw SQL
|
||||||
|
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
...
|
||||||
|
rows.Scan(&name, &age, &email)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scan Rows
|
||||||
|
|
||||||
|
```go
|
||||||
|
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var user User
|
||||||
|
db.ScanRows(rows, &user)
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,41 @@
|
||||||
|
## Transactions
|
||||||
|
|
||||||
|
To perform a set of operations within a transaction, the general flow is as below.
|
||||||
|
The database handle returned from ``` db.Begin() ``` should be used for all operations within the transaction.
|
||||||
|
(Note that all individual save and delete operations are run in a transaction by default.)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// begin
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
// do some database operations (use 'tx' from this point, not 'db')
|
||||||
|
tx.Create(...)
|
||||||
|
...
|
||||||
|
|
||||||
|
// rollback in case of error
|
||||||
|
tx.Rollback()
|
||||||
|
|
||||||
|
// Or commit if all is ok
|
||||||
|
tx.Commit()
|
||||||
|
```
|
||||||
|
|
||||||
|
### A Specific Example
|
||||||
|
```
|
||||||
|
func CreateAnimals(db *gorm.DB) err {
|
||||||
|
tx := db.Begin()
|
||||||
|
// Note the use of tx as the database handle once you are within a transaction
|
||||||
|
|
||||||
|
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Association Mode
|
||||||
|
|
||||||
|
Association Mode contains some helper methods to handle relationship things easily.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Start Association Mode
|
||||||
|
var user User
|
||||||
|
db.Model(&user).Association("Languages")
|
||||||
|
// `user` is the source, it need to be a valid record (contains primary key)
|
||||||
|
// `Languages` is source's field name for a relationship.
|
||||||
|
// If those conditions not matched, will return an error, check it with:
|
||||||
|
// db.Model(&user).Association("Languages").Error
|
||||||
|
|
||||||
|
|
||||||
|
// Query - Find out all related associations
|
||||||
|
db.Model(&user).Association("Languages").Find(&languages)
|
||||||
|
|
||||||
|
|
||||||
|
// Append - Append new associations for many2many, has_many, will replace current association for has_one, belongs_to
|
||||||
|
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
|
||||||
|
db.Model(&user).Association("Languages").Append(Language{Name: "DE"})
|
||||||
|
|
||||||
|
|
||||||
|
// Delete - Remove relationship between source & passed arguments, won't delete those arguments
|
||||||
|
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
|
||||||
|
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
|
||||||
|
|
||||||
|
|
||||||
|
// Replace - Replace current associations with new one
|
||||||
|
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
|
||||||
|
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
|
||||||
|
|
||||||
|
|
||||||
|
// Count - Return the count of current associations
|
||||||
|
db.Model(&user).Association("Languages").Count()
|
||||||
|
|
||||||
|
|
||||||
|
// Clear - Remove relationship between source & current associations, won't delete those associations
|
||||||
|
db.Model(&user).Association("Languages").Clear()
|
||||||
|
```
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Associations
|
||||||
|
|
||||||
|
{% for section in book.chapters["associations/associations.md"].sections %}
|
||||||
|
* [**{{section.name}}**](../{{section.path}})
|
||||||
|
{% if section["sections"] %}{% for subsection in section.sections %}
|
||||||
|
* [**{{ subsection.name }}**]({{ subsection.path }})
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
{% endfor %}
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Belongs To
|
||||||
|
|
||||||
|
```go
|
||||||
|
// User belongs to a profile, ProfileID is the foreign key
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Profile Profile
|
||||||
|
ProfileID int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Model(&user).Related(&profile)
|
||||||
|
//// SELECT * FROM profiles WHERE id = 111; // 111 is user's foreign key ProfileID
|
||||||
|
```
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Has Many
|
||||||
|
|
||||||
|
```go
|
||||||
|
// User has many emails, UserID is the foreign key
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Emails []Email
|
||||||
|
}
|
||||||
|
|
||||||
|
type Email struct {
|
||||||
|
gorm.Model
|
||||||
|
Email string
|
||||||
|
UserID uint
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Model(&user).Related(&emails)
|
||||||
|
//// SELECT * FROM emails WHERE user_id = 111; // 111 is user's primary key
|
||||||
|
```
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Has One
|
||||||
|
|
||||||
|
```go
|
||||||
|
// User has one CreditCard, UserID is the foreign key
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
CreditCard CreditCard
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreditCard struct {
|
||||||
|
gorm.Model
|
||||||
|
UserID uint
|
||||||
|
Number string
|
||||||
|
}
|
||||||
|
|
||||||
|
var card CreditCard
|
||||||
|
db.Model(&user).Related(&card, "CreditCard")
|
||||||
|
//// SELECT * FROM credit_cards WHERE user_id = 123; // 123 is user's primary key
|
||||||
|
// CreditCard is user's field name, it means get user's CreditCard relations and fill it into variable card
|
||||||
|
// If the field name is same as the variable's type name, like above example, it could be omitted, like:
|
||||||
|
db.Model(&user).Related(&card)
|
||||||
|
```
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Many To Many
|
||||||
|
|
||||||
|
```go
|
||||||
|
// User has and belongs to many languages, use `user_languages` as join table
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Languages []Language `gorm:"many2many:user_languages;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Language struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Model(&user).Related(&languages)
|
||||||
|
//// SELECT * FROM "languages" INNER JOIN "user_languages" ON "user_languages"."language_id" = "languages"."id" WHERE "user_languages"."user_id" = 111
|
||||||
|
```
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Polymorphism
|
||||||
|
|
||||||
|
Supports polymorphic has-many and has-one associations.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Cat struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Toy Toy `gorm:"polymorphic:Owner;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dog struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Toy Toy `gorm:"polymorphic:Owner;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Toy struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
OwnerId int
|
||||||
|
OwnerType string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: polymorphic belongs-to and many-to-many are explicitly NOT supported, and will throw errors.
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Callbacks
|
||||||
|
|
||||||
|
Callbacks are methods defined on the pointer of struct.
|
||||||
|
If any callback returns an error, gorm will stop future operations and rollback all changes.
|
||||||
|
|
||||||
|
Here is the list of all available callbacks:
|
||||||
|
(listed in the same order in which they will get called during the respective operations)
|
||||||
|
|
||||||
|
### Creating An Object
|
||||||
|
|
||||||
|
```go
|
||||||
|
BeforeSave
|
||||||
|
BeforeCreate
|
||||||
|
// save before associations
|
||||||
|
// save self
|
||||||
|
// save after associations
|
||||||
|
AfterCreate
|
||||||
|
AfterSave
|
||||||
|
```
|
||||||
|
### Updating An Object
|
||||||
|
|
||||||
|
```go
|
||||||
|
BeforeSave
|
||||||
|
BeforeUpdate
|
||||||
|
// save before associations
|
||||||
|
// save self
|
||||||
|
// save after associations
|
||||||
|
AfterUpdate
|
||||||
|
AfterSave
|
||||||
|
```
|
||||||
|
|
||||||
|
### Destroying An Object
|
||||||
|
|
||||||
|
```go
|
||||||
|
BeforeDelete
|
||||||
|
// delete self
|
||||||
|
AfterDelete
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Find
|
||||||
|
|
||||||
|
```go
|
||||||
|
// load data from database
|
||||||
|
AfterFind
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (u *User) BeforeUpdate() (err error) {
|
||||||
|
if u.readonly() {
|
||||||
|
err = errors.New("read only user")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback the insertion if user's id greater than 1000
|
||||||
|
func (u *User) AfterCreate() (err error) {
|
||||||
|
if (u.Id > 1000) {
|
||||||
|
err = errors.New("user id is already greater than 1000")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Save/delete operations in gorm are running in a transaction.
|
||||||
|
Changes made in that transaction are not visible unless it is commited.
|
||||||
|
So if you want to use those changes in your callbacks, you need to run your SQL in the same transaction.
|
||||||
|
For this Gorm supports passing transactions to callbacks like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
|
||||||
|
tx.Model(u).Update("role", "admin")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Create
|
||||||
|
|
||||||
|
### Create Record
|
||||||
|
|
||||||
|
```go
|
||||||
|
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
|
||||||
|
|
||||||
|
db.NewRecord(user) // => returns `true` as primary key is blank
|
||||||
|
|
||||||
|
db.Create(&user)
|
||||||
|
|
||||||
|
db.NewRecord(user) // => return `false` after `user` created
|
||||||
|
|
||||||
|
// Associations will be inserted automatically when save the record
|
||||||
|
user := User{
|
||||||
|
Name: "jinzhu",
|
||||||
|
BillingAddress: Address{Address1: "Billing Address - Address 1"},
|
||||||
|
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
|
||||||
|
Emails: []Email{{Email: "jinzhu@example.com"}, {Email: "jinzhu-2@example@example.com"}},
|
||||||
|
Languages: []Language{{Name: "ZH"}, {Name: "EN"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Create(&user)
|
||||||
|
//// BEGIN TRANSACTION;
|
||||||
|
//// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1");
|
||||||
|
//// INSERT INTO "addresses" (address1) VALUES ("Shipping Address - Address 1");
|
||||||
|
//// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
|
||||||
|
//// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com");
|
||||||
|
//// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu-2@example.com");
|
||||||
|
//// INSERT INTO "languages" ("name") VALUES ('ZH');
|
||||||
|
//// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 1);
|
||||||
|
//// INSERT INTO "languages" ("name") VALUES ('EN');
|
||||||
|
//// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 2);
|
||||||
|
//// COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create With Associations
|
||||||
|
|
||||||
|
Refer Associations for more details
|
||||||
|
|
||||||
|
### Default Values
|
||||||
|
|
||||||
|
You could defined default value in the `sql` tag, then the generated creating SQL will ignore these fields that including default value and its value is blank, and after inserted the record into databae, gorm will load those fields's value from database.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Animal struct {
|
||||||
|
ID int64
|
||||||
|
Name string `sql:"default:'galeone'"`
|
||||||
|
Age int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var animal = Animal{Age: 99, Name: ""}
|
||||||
|
db.Create(&animal)
|
||||||
|
// INSERT INTO animals("age") values('99');
|
||||||
|
// SELECT name from animals WHERE ID=111; // the returning primary key is 111
|
||||||
|
// animal.Name => 'galeone'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Primary Key In Callbacks
|
||||||
|
|
||||||
|
If you want to set primary key in `BeforeCreate` callback, you could use `scope.SetColumn`, for example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (user *User) BeforeCreate(scope *gorm.Scope) error {
|
||||||
|
scope.SetColumn("ID", uuid.New())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extra Creating option
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Add extra SQL option for inserting SQL
|
||||||
|
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
|
||||||
|
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;
|
|
@ -0,0 +1,9 @@
|
||||||
|
# CRUD: Reading and Writing Data
|
||||||
|
|
||||||
|
{% for section in book.chapters["curd/curd.md"].sections %}
|
||||||
|
* [**{{section.name}}**](../{{section.path}})
|
||||||
|
{% if section["sections"] %}{% for subsection in section.sections %}
|
||||||
|
* [**{{ subsection.name }}**]({{ subsection.path }})
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Delete
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Delete an existing record
|
||||||
|
db.Delete(&email)
|
||||||
|
//// DELETE from emails where id=10;
|
||||||
|
|
||||||
|
// Add extra SQL option for deleting SQL
|
||||||
|
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
|
||||||
|
//// DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Delete
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
|
||||||
|
//// DELETE from emails where email LIKE "%jinhu%";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Soft Delete
|
||||||
|
|
||||||
|
If struct has `DeletedAt` field, it will get soft delete ability automatically!
|
||||||
|
Then it won't be deleted from database permanently when call `Delete`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Delete(&user)
|
||||||
|
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
|
||||||
|
|
||||||
|
// Batch Delete
|
||||||
|
db.Where("age = ?", 20).Delete(&User{})
|
||||||
|
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
|
||||||
|
|
||||||
|
// Soft deleted records will be ignored when query them
|
||||||
|
db.Where("age = 20").Find(&user)
|
||||||
|
//// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
// Find soft deleted records with Unscoped
|
||||||
|
db.Unscoped().Where("age = 20").Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE age = 20;
|
||||||
|
|
||||||
|
// Delete record permanently with Unscoped
|
||||||
|
db.Unscoped().Delete(&order)
|
||||||
|
//// DELETE FROM orders WHERE id=10;
|
||||||
|
```
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Preloading (Eager loading)
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Preload("Orders").Find(&users)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4);
|
||||||
|
|
||||||
|
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
|
||||||
|
|
||||||
|
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE state = 'active';
|
||||||
|
//// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
|
||||||
|
|
||||||
|
db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
|
||||||
|
//// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
|
||||||
|
//// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Preloading SQL
|
||||||
|
|
||||||
|
You could custom preloading SQL by passing in `func(db *gorm.DB) *gorm.DB` (same type as the one used for [Scopes](#scopes)), for example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Order("orders.amount DESC")
|
||||||
|
}).Find(&users)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Nested Preloading
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Preload("Orders.OrderItems").Find(&users)
|
||||||
|
db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)
|
||||||
|
```
|
|
@ -0,0 +1,426 @@
|
||||||
|
# Query
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get the first record
|
||||||
|
db.First(&user)
|
||||||
|
//// SELECT * FROM users ORDER BY id LIMIT 1;
|
||||||
|
|
||||||
|
// Get the last record
|
||||||
|
db.Last(&user)
|
||||||
|
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;
|
||||||
|
|
||||||
|
// Get all records
|
||||||
|
db.Find(&users)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
|
||||||
|
// Get record with primary key
|
||||||
|
db.First(&user, 10)
|
||||||
|
//// SELECT * FROM users WHERE id = 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query With Where (Plain SQL)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get the first matched record
|
||||||
|
db.Where("name = ?", "jinzhu").First(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;
|
||||||
|
|
||||||
|
// Get all matched records
|
||||||
|
db.Where("name = ?", "jinzhu").Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE name = 'jinzhu';
|
||||||
|
|
||||||
|
db.Where("name <> ?", "jinzhu").Find(&users)
|
||||||
|
|
||||||
|
// IN
|
||||||
|
db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)
|
||||||
|
|
||||||
|
// LIKE
|
||||||
|
db.Where("name LIKE ?", "%jin%").Find(&users)
|
||||||
|
|
||||||
|
// AND
|
||||||
|
db.Where("name = ? and age >= ?", "jinzhu", "22").Find(&users)
|
||||||
|
|
||||||
|
// Time
|
||||||
|
db.Where("updated_at > ?", lastWeek).Find(&users)
|
||||||
|
|
||||||
|
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query With Where (Struct & Map)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Struct
|
||||||
|
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
|
||||||
|
|
||||||
|
// Map
|
||||||
|
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
|
||||||
|
|
||||||
|
// Slice of primary keys
|
||||||
|
db.Where([]int64{20, 21, 22}).Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE id IN (20, 21, 22);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query With Not
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Not("name", "jinzhu").First(&user)
|
||||||
|
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;
|
||||||
|
|
||||||
|
// Not In
|
||||||
|
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
|
||||||
|
|
||||||
|
// Not In slice of primary keys
|
||||||
|
db.Not([]int64{1,2,3}).First(&user)
|
||||||
|
//// SELECT * FROM users WHERE id NOT IN (1,2,3);
|
||||||
|
|
||||||
|
db.Not([]int64{}).First(&user)
|
||||||
|
//// SELECT * FROM users;
|
||||||
|
|
||||||
|
// Plain SQL
|
||||||
|
db.Not("name = ?", "jinzhu").First(&user)
|
||||||
|
//// SELECT * FROM users WHERE NOT(name = "jinzhu");
|
||||||
|
|
||||||
|
// Struct
|
||||||
|
db.Not(User{Name: "jinzhu"}).First(&user)
|
||||||
|
//// SELECT * FROM users WHERE name <> "jinzhu";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query With Inline Condition
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get by primary key
|
||||||
|
db.First(&user, 23)
|
||||||
|
//// SELECT * FROM users WHERE id = 23 LIMIT 1;
|
||||||
|
|
||||||
|
// Plain SQL
|
||||||
|
db.Find(&user, "name = ?", "jinzhu")
|
||||||
|
//// SELECT * FROM users WHERE name = "jinzhu";
|
||||||
|
|
||||||
|
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
|
||||||
|
//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
|
||||||
|
|
||||||
|
// Struct
|
||||||
|
db.Find(&users, User{Age: 20})
|
||||||
|
//// SELECT * FROM users WHERE age = 20;
|
||||||
|
|
||||||
|
// Map
|
||||||
|
db.Find(&users, map[string]interface{}{"age": 20})
|
||||||
|
//// SELECT * FROM users WHERE age = 20;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query With Or
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
|
||||||
|
|
||||||
|
// Struct
|
||||||
|
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';
|
||||||
|
|
||||||
|
// Map
|
||||||
|
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query Chains
|
||||||
|
|
||||||
|
Gorm has a chainable API, you could use it like this
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Where("name <> ?","jinzhu").Where("age >= ? and role <> ?",20,"admin").Find(&users)
|
||||||
|
//// SELECT * FROM users WHERE name <> 'jinzhu' AND age >= 20 AND role <> 'admin';
|
||||||
|
|
||||||
|
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extra Querying option
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Add extra SQL option for selecting SQL
|
||||||
|
db.Set("gorm:query_option", "FOR UPDATE").First(&user, 10)
|
||||||
|
//// SELECT * FROM users WHERE id = 10 FOR UPDATE;
|
||||||
|
```
|
||||||
|
|
||||||
|
## FirstOrInit
|
||||||
|
|
||||||
|
Get the first matched record, or initialize a record with search conditions.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.FirstOrInit(&user, User{Name: "non_existing"})
|
||||||
|
//// user -> User{Name: "non_existing"}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "Jinzhu"}).FirstOrInit(&user)
|
||||||
|
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
|
||||||
|
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
|
||||||
|
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attrs
|
||||||
|
|
||||||
|
Ignore some values when searching, but use them to initialize the struct if record is not found.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
|
||||||
|
//// SELECT * FROM USERS WHERE name = 'non_existing';
|
||||||
|
//// user -> User{Name: "non_existing", Age: 20}
|
||||||
|
|
||||||
|
db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user)
|
||||||
|
//// SELECT * FROM USERS WHERE name = 'non_existing';
|
||||||
|
//// user -> User{Name: "non_existing", Age: 20}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
|
||||||
|
//// SELECT * FROM USERS WHERE name = jinzhu';
|
||||||
|
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assign
|
||||||
|
|
||||||
|
Ignore some values when searching, but assign it to the result regardless it is found or not.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
|
||||||
|
//// user -> User{Name: "non_existing", Age: 20}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)
|
||||||
|
//// SELECT * FROM USERS WHERE name = jinzhu';
|
||||||
|
//// user -> User{Id: 111, Name: "Jinzhu", Age: 30}
|
||||||
|
```
|
||||||
|
|
||||||
|
## FirstOrCreate
|
||||||
|
|
||||||
|
Get the first matched record, or create with search conditions.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.FirstOrCreate(&user, User{Name: "non_existing"})
|
||||||
|
//// INSERT INTO "users" (name) VALUES ("non_existing");
|
||||||
|
//// user -> User{Id: 112, Name: "non_existing"}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
|
||||||
|
//// user -> User{Id: 111, Name: "Jinzhu"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attrs
|
||||||
|
|
||||||
|
Ignore some values when searching, but use them to create the struct if record is not found. like `FirstOrInit`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = 'non_existing';
|
||||||
|
//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
|
||||||
|
//// user -> User{Id: 112, Name: "non_existing", Age: 20}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = 'jinzhu';
|
||||||
|
//// user -> User{Id: 111, Name: "jinzhu", Age: 20}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assign
|
||||||
|
|
||||||
|
Ignore some values when searching, but assign it to the record regardless it is found or not, then save back to database. like `FirstOrInit`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Unfound
|
||||||
|
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = 'non_existing';
|
||||||
|
//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
|
||||||
|
//// user -> User{Id: 112, Name: "non_existing", Age: 20}
|
||||||
|
|
||||||
|
// Found
|
||||||
|
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
|
||||||
|
//// SELECT * FROM users WHERE name = 'jinzhu';
|
||||||
|
//// UPDATE users SET age=30 WHERE id = 111;
|
||||||
|
//// user -> User{Id: 111, Name: "jinzhu", Age: 30}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Select
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Select("name, age").Find(&users)
|
||||||
|
//// SELECT name, age FROM users;
|
||||||
|
|
||||||
|
db.Select([]string{"name", "age"}).Find(&users)
|
||||||
|
//// SELECT name, age FROM users;
|
||||||
|
|
||||||
|
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
|
||||||
|
//// SELECT COALESCE(age,'42') FROM users;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Order
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Order("age desc, name").Find(&users)
|
||||||
|
//// SELECT * FROM users ORDER BY age desc, name;
|
||||||
|
|
||||||
|
// Multiple orders
|
||||||
|
db.Order("age desc").Order("name").Find(&users)
|
||||||
|
//// SELECT * FROM users ORDER BY age desc, name;
|
||||||
|
|
||||||
|
// ReOrder
|
||||||
|
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
|
||||||
|
//// SELECT * FROM users ORDER BY age desc; (users1)
|
||||||
|
//// SELECT * FROM users ORDER BY age; (users2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limit
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Limit(3).Find(&users)
|
||||||
|
//// SELECT * FROM users LIMIT 3;
|
||||||
|
|
||||||
|
// Cancel limit condition with -1
|
||||||
|
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
|
||||||
|
//// SELECT * FROM users LIMIT 10; (users1)
|
||||||
|
//// SELECT * FROM users; (users2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Offset
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Offset(3).Find(&users)
|
||||||
|
//// SELECT * FROM users OFFSET 3;
|
||||||
|
|
||||||
|
// Cancel offset condition with -1
|
||||||
|
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
|
||||||
|
//// SELECT * FROM users OFFSET 10; (users1)
|
||||||
|
//// SELECT * FROM users; (users2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Count
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
|
||||||
|
//// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
|
||||||
|
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)
|
||||||
|
|
||||||
|
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
|
||||||
|
//// SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)
|
||||||
|
|
||||||
|
db.Table("deleted_users").Count(&count)
|
||||||
|
//// SELECT count(*) FROM deleted_users;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Group & Having
|
||||||
|
|
||||||
|
```go
|
||||||
|
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
|
||||||
|
for rows.Next() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
|
||||||
|
for rows.Next() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Date time.Time
|
||||||
|
Total int64
|
||||||
|
}
|
||||||
|
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Joins
|
||||||
|
|
||||||
|
```go
|
||||||
|
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
|
||||||
|
for rows.Next() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
|
||||||
|
|
||||||
|
// multiple joins with parameter
|
||||||
|
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pluck
|
||||||
|
|
||||||
|
Get selected attributes as map
|
||||||
|
|
||||||
|
```go
|
||||||
|
var ages []int64
|
||||||
|
db.Find(&users).Pluck("age", &ages)
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
db.Model(&User{}).Pluck("name", &names)
|
||||||
|
|
||||||
|
db.Table("deleted_users").Pluck("name", &names)
|
||||||
|
|
||||||
|
// Requesting more than one column? Do it like this:
|
||||||
|
db.Select("name, age").Find(&users)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scan
|
||||||
|
|
||||||
|
Scan results into another struct.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Result struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Result
|
||||||
|
db.Table("users").Select("name, age").Where("name = ?", 3).Scan(&result)
|
||||||
|
|
||||||
|
// Raw SQL
|
||||||
|
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scopes
|
||||||
|
|
||||||
|
```go
|
||||||
|
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("amount > ?", 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("pay_mode_sign = ?", "C")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PaidWithCod(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("pay_mode_sign = ?", "C")
|
||||||
|
}
|
||||||
|
|
||||||
|
func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
|
||||||
|
return func (db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Scopes(AmountGreaterThan1000).Where("status in (?)", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
|
||||||
|
// Find all credit card orders and amount greater than 1000
|
||||||
|
|
||||||
|
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
|
||||||
|
// Find all COD orders and amount greater than 1000
|
||||||
|
|
||||||
|
db.Scopes(OrderStatus([]string{"paid", "shipped"})).Find(&orders)
|
||||||
|
// Find all paid, shipped orders
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specifying The Table Name
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create `deleted_users` table with struct User's definition
|
||||||
|
db.Table("deleted_users").CreateTable(&User{})
|
||||||
|
|
||||||
|
var deleted_users []User
|
||||||
|
db.Table("deleted_users").Find(&deleted_users)
|
||||||
|
//// SELECT * FROM deleted_users;
|
||||||
|
|
||||||
|
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
|
||||||
|
//// DELETE FROM deleted_users WHERE name = 'jinzhu';
|
||||||
|
```
|
|
@ -0,0 +1,120 @@
|
||||||
|
# Update
|
||||||
|
|
||||||
|
### Update All Fields
|
||||||
|
|
||||||
|
`Save` will include all fields when perform the Updating SQL, even it is not changed
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.First(&user)
|
||||||
|
|
||||||
|
user.Name = "jinzhu 2"
|
||||||
|
user.Age = 100
|
||||||
|
db.Save(&user)
|
||||||
|
|
||||||
|
//// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Changed Fields
|
||||||
|
|
||||||
|
If you only want to update changed Fields, you could use `Update`, `Updates`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Update single attribute if it is changed
|
||||||
|
db.Model(&user).Update("name", "hello")
|
||||||
|
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
|
||||||
|
|
||||||
|
// Update single attribute with combined conditions
|
||||||
|
db.Model(&user).Where("active = ?", true).Update("name", "hello")
|
||||||
|
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
|
||||||
|
|
||||||
|
|
||||||
|
// Update multiple attributes with `map`, will only update those changed fields
|
||||||
|
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
|
||||||
|
//// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
|
||||||
|
|
||||||
|
// Update multiple attributes with `struct`, will only update those changed & non blank fields
|
||||||
|
db.Model(&user).Updates(User{Name: "hello", Age: 18})
|
||||||
|
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
|
||||||
|
|
||||||
|
// WARNING when update with struct, GORM will only update those fields that with non blank value
|
||||||
|
// For below Update, nothing will be updated as "", 0, false are blank values of their types
|
||||||
|
db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Selected Fields
|
||||||
|
|
||||||
|
If you only want to update or ignore some fields when updating, you could use `Select`, `Omit`
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
|
||||||
|
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
|
||||||
|
|
||||||
|
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
|
||||||
|
//// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Changed Fields Without Callbacks
|
||||||
|
|
||||||
|
Updating operations above will invoke `BeforeUpdate`, `AfterUpdate`, Update UpdatedAt timestamp, Save Associations callbacks, if you don't call them, you could use `UpdateColumn`, `UpdateColumns`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Update single attribute, similar with `Update`
|
||||||
|
db.Model(&user).UpdateColumn("name", "hello")
|
||||||
|
//// UPDATE users SET name='hello' WHERE id = 111;
|
||||||
|
|
||||||
|
// Update multiple attributes, similar with `Updates`
|
||||||
|
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
|
||||||
|
//// UPDATE users SET name='hello', age=18 WHERE id = 111;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Updates
|
||||||
|
|
||||||
|
Callbacks won't run when do batch updates
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
|
||||||
|
//// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
|
||||||
|
|
||||||
|
// Update with struct only works with none zero values, or use map[string]interface{}
|
||||||
|
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
|
||||||
|
//// UPDATE users SET name='hello', age=18;
|
||||||
|
|
||||||
|
// Get updated records count with `RowsAffected`
|
||||||
|
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update with SQL Expression
|
||||||
|
|
||||||
|
```go
|
||||||
|
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
|
||||||
|
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';
|
||||||
|
|
||||||
|
DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
|
||||||
|
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';
|
||||||
|
|
||||||
|
DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
|
||||||
|
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2';
|
||||||
|
|
||||||
|
DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
|
||||||
|
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2' AND quantity > 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Updating Values In Callbacks
|
||||||
|
|
||||||
|
If you want to change updating values in callbacks using `BeforeUpdate`, `BeforeSave`, you could use `scope.SetColumn`, for example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (user *User) BeforeSave(scope *gorm.Scope) (err error) {
|
||||||
|
if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
|
||||||
|
scope.SetColumn("EncryptedPassword", pw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extra Updating option
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Add extra SQL option for updating SQL
|
||||||
|
db.Model(&user).Set("gorm:update_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Update("name, "hello")
|
||||||
|
//// UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111 OPTION (OPTIMIZE FOR UNKNOWN);
|
||||||
|
```
|
|
@ -6,20 +6,31 @@
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int
|
gorm.Model
|
||||||
Birthday time.Time
|
Birthday time.Time
|
||||||
Age int
|
Age int
|
||||||
Name string `sql:"size:255"` // Default size for string is 255, you could reset it with this tag
|
Name string `sql:"size:255"` // Default size for string is 255, you could reset it with this tag
|
||||||
Num int `sql:"AUTO_INCREMENT"`
|
Num int `sql:"AUTO_INCREMENT"`
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt *time.Time
|
|
||||||
IgnoreMe int `sql:"-"` // Ignore this field
|
IgnoreMe int `sql:"-"` // Ignore this field
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Conventions & Overriding Conventions
|
## Conventions & Overriding Conventions
|
||||||
|
|
||||||
|
### `gorm.Model` struct
|
||||||
|
|
||||||
|
Gorm has defined struct `gorm.Model`, which could be embeded in your models, it will add fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` to your model
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Model's definition
|
||||||
|
type Model struct {
|
||||||
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt *time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Table name is the pluralized version of struct name
|
### Table name is the pluralized version of struct name
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -30,8 +41,16 @@ type (User) TableName() string {
|
||||||
return "profiles"
|
return "profiles"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or disable table name's pluralization globally
|
func (u User) TableName() string {
|
||||||
db.SingularTable(true) // if set this to true, then default table name will be `user`, table name setted with `TableName` won't be affected
|
if u.Role == "admin" {
|
||||||
|
return "admin_users"
|
||||||
|
} else {
|
||||||
|
return "users"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable table name's pluralization globally
|
||||||
|
db.SingularTable(true) // if set this to true, `User`'s default table name will be `user`, table name setted with `TableName` won't be affected
|
||||||
```
|
```
|
||||||
|
|
||||||
### Column name is the snake case of field's name
|
### Column name is the snake case of field's name
|
||||||
|
@ -45,37 +64,48 @@ type User struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Animal struct {
|
type Animal struct {
|
||||||
AnimalId int64 `gorm:"column:beast_id"` // set column name to be `beast_id`
|
AnimalId int64 `gorm:"column:beast_id"` // set column name to `beast_id`
|
||||||
Birthday time.Time `gorm:"column:day_of_the_beast"` // set column name to be `day_of_the_beast`
|
Birthday time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
|
||||||
Age int64 `gorm:"column:age_of_the_beast"` // set column name to be `age_of_the_beast`
|
Age int64 `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Default use field `ID` as primary key
|
### Field `ID` as primary key
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint // field `ID` is the default primary key for `User`
|
ID uint // field named `ID` is the default primary key for `User`
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// your could also use tag `primary_key` to set other field as primary key
|
||||||
type Animal struct {
|
type Animal struct {
|
||||||
// tag `primary_key` used to set `AnimalId` to be primary key
|
AnimalId int64 `gorm:"primary_key"` // set AnimalId to be primary key
|
||||||
AnimalId int64 `gorm:"primary_key"`
|
|
||||||
Name string
|
Name string
|
||||||
Age int64
|
Age int64
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use `CreatedAt` to store record's created time if field exists
|
### Field `CreatedAt` used to store record's created time
|
||||||
|
|
||||||
|
Create records having `CreatedAt` field will set it to current time.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
db.Create(&user) // will set field `CreatedAt`'s time to time now
|
db.Create(&user) // will set `CreatedAt` to current time
|
||||||
|
|
||||||
// If you want to change its value, use `Update`
|
// To change its value, you could use `Update`
|
||||||
db.Model(&user).Update("CreatedAt", time.Now())
|
db.Model(&user).Update("CreatedAt", time.Now())
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use `UpdatedAt` to store record's updated time if field exists
|
### Use `UpdatedAt` used to store record's updated time
|
||||||
|
|
||||||
|
Save records having `UpdatedAt` field will set it to current time.
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Save(&user) // will set `UpdatedAt` to current time
|
||||||
|
db.Model(&user).Update("name", "jinzhu") // will set `UpdatedAt` to current time
|
||||||
|
```
|
||||||
|
|
||||||
### Use `DeletedAt` to store record's deleted time if field exists
|
### Use `DeletedAt` to store record's deleted time if field exists
|
||||||
### Gorm provide a default model struct, you could embed it in your struct
|
|
||||||
|
Delete records having `DeletedAt` field, it won't delete the record from database, but will set field `DeletedAt`'s value to current time.
|
||||||
|
|
Loading…
Reference in New Issue