gorm/README.md

505 lines
15 KiB
Markdown
Raw Normal View History

2013-10-25 12:24:29 +04:00
# GORM
2013-10-26 11:18:44 +04:00
Yet Another ORM library for Go, aims for developer friendly
2013-10-25 12:24:29 +04:00
2013-10-28 06:09:44 +04:00
## Overview
* CURD
* Chainable API
2013-11-02 17:02:54 +04:00
* Embedded structs support
2013-10-28 06:09:44 +04:00
* Before/After Create/Save/Update/Delete Callbacks
2013-10-28 17:52:22 +04:00
* Update, Updates Like Rails's update_attribute, update_attributes
2013-10-29 16:32:27 +04:00
* FirstOrInit, FirstOrCreate Like Rails's first_or_initialize, first_or_create
2013-11-02 17:02:54 +04:00
* Order/Select/Limit/Offset Support
* Automatically CreatedAt, UpdatedAt
* Soft Delete
2013-11-02 17:02:54 +04:00
* Create/Drop table from struct
* Dynamically set table name when search, create, update, delete...
2013-10-28 06:09:44 +04:00
* Prevent SQL Injection
* Goroutines friendly
* Database Pool
2013-11-02 17:02:54 +04:00
* Convention Over Configuration (CoC)
2013-10-28 06:09:44 +04:00
2013-10-27 18:18:06 +04:00
## Basic Usage
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
## Opening a Database
```go
db, err = Open("postgres", "user=gorm dbname=gorm sslmode=disable")
// Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere
var DB gorm.DB
func init() {
DB, err = gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")
if err != nil {
panic(fmt.Sprintf("Got error when connect database, the error is '%v'", err))
}
}
// Set the maximum idle database connections
db.SetPool(100)
```
## Conventions
2013-10-27 17:37:31 +04:00
```go
2013-11-02 17:02:54 +04:00
type User struct { // TableName: `users`, gorm will pluralize struct name as table name
Id int64 // Id: Database Primary key
Birthday time.Time // Time
Age int64
Name string
CreatedAt time.Time // CreatedAt: Time of record is created, will be insert automatically
UpdatedAt time.Time // UpdatedAt: Time of record is updated, will be updated automatically
DeletedAt time.Time // DeletedAt: Time of record is deleted, refer Soft Delete for more
Email []Email // Embedded structs
BillingAddress Address // Embedded struct
BillingAddressId int64 // Embedded struct's foreign key
ShippingAddress Address // Embedded struct
ShippingAddressId int64 // Embedded struct's foreign key
2013-10-27 17:37:31 +04:00
}
2013-11-02 17:02:54 +04:00
type Email struct { // TableName: `emails`
Id int64
UserId int64 // Foreign key for above embedded structs
Email string
Subscribed bool
}
2013-10-28 06:09:44 +04:00
2013-11-02 17:02:54 +04:00
type Address struct { // TableName: `addresses`
Id int64
Address1 string
Address2 string
Post string
}
```
## Struct & Database Mapping
```go
// Create table from struct
db.CreateTable(User{})
// Drop table
db.DropTable(User{})
```
## Create
```go
user := User{Name: "jinzhu", Age: 18, Birthday: time.Now()}
2013-10-27 17:37:31 +04:00
db.Save(&user)
2013-11-02 17:02:54 +04:00
```
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
## Update
```go
2013-10-27 17:37:31 +04:00
user.Name = "jinzhu 2"
db.Save(&user)
2013-11-02 17:02:54 +04:00
```
## Delete
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
```go
2013-10-27 17:37:31 +04:00
db.Delete(&user)
2013-11-02 17:02:54 +04:00
```
## Query
```go
// Get the first record
db.First(&user)
//// SELECT * FROM USERS LIMIT 1;
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
// Get All records
db.Find(&users)
//// SELECT * FROM USERS;
// Using a Primary Key
db.First(&user, 10)
//// SELECT * FROM USERS WHERE id = 10;
```
### Where (SQL like condition)
```go
// Get the first matched record
2013-10-27 17:37:31 +04:00
db.Where("name = ?", "jinzhu").First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
// Get all matched records
2013-10-27 17:37:31 +04:00
db.Where("name = ?", "jinzhu").Find(&users)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name = 'jinzhu';
2013-10-27 17:37:31 +04:00
db.Where("name <> ?", "jinzhu").Find(&users)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
2013-10-27 17:37:31 +04:00
db.Where("name in (?)", []string["jinzhu", "jinzhu 2"]).Find(&users)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name IN ('jinzhu', 'jinzhu 2');
// IN For Primary Key
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
// LIKE
2013-10-28 05:05:44 +04:00
db.Where("name LIKE ?", "%jin%").Find(&users)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name LIKE "%jin%";
// Multiple Conditions
db.Where("name = ? and age >= ?", "jinzhu", "22").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
```
### Where (Struct & Map)
```go
// Search with struct
2013-10-29 13:37:45 +04:00
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
// Search with map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
```
### Not
```go
// Not Equal
db.Not("name", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;
2013-10-27 17:37:31 +04:00
2013-11-02 17:02:54 +04:00
// Not In
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Not In for Primary Key
2013-10-31 14:12:18 +04:00
db.Not([]int64{1,2,3}).First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE id NOT IN (1,2,3);
2013-10-31 14:12:18 +04:00
db.Not([]int64{}).First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users;
// Normal SQL
2013-10-31 18:49:48 +04:00
db.Not("name = ?", "jinzhu").First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE NOT(name = "jinzhu");
// Not With Struct
2013-10-31 14:12:18 +04:00
db.Not(User{Name: "jinzhu"}).First(&user)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name <> "jinzhu";
```
### Inline Search Condition
2013-10-31 14:12:18 +04:00
2013-11-02 17:02:54 +04:00
```go
// Find with primary key
2013-10-27 18:36:43 +04:00
db.First(&user, 23)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE id = 23 LIMIT 1;
// Normal SQL
db.Find(&user, "name = ?", "jinzhu")
//// SELECT * FROM users WHERE name = "jinzhu";
// Multiple Conditions
2013-10-27 18:36:43 +04:00
db.Find(&users, "name <> ? and age > ?", "jinzhu", 20)
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Inline Search With Struct
db.Find(&users, User{Age: 20})
//// SELECT * FROM users WHERE age = 20;
// Inline Search With Map
2013-10-29 13:52:37 +04:00
db.Find(&users, map[string]interface{}{"age": 20})
2013-11-02 17:02:54 +04:00
//// SELECT * FROM users WHERE age = 20;
```
## FirstOrInit
Try to load the first record, if fails, initialize struct with search conditions.
(only support map or struct conditions, SQL like conditions are not supported)
```go
db.FirstOrInit(&user, User{Name: "non_existing"})
//// User{Name: "non_existing"}
2013-10-27 18:18:06 +04:00
2013-10-29 16:32:27 +04:00
db.Where(User{Name: "Jinzhu"}).FirstOrInit(&user)
2013-11-02 17:02:54 +04:00
//// User{Id: 111, Name: "Jinzhu", Age: 20}
2013-10-29 16:32:27 +04:00
2013-11-02 17:02:54 +04:00
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
//// User{Id: 111, Name: "Jinzhu", Age: 20}
```
### FirstOrInit With Attrs
Attr's arguments would be used to initialize struct if not record found, but won't be used for search
```go
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = 'non_existing';
//// User{Name: "non_existing", Age: 20}
// Above code could be simplified if have only one attribute
db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user)
2013-11-02 17:02:54 +04:00
// If record found, Attrs would be ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = jinzhu';
2013-10-31 05:34:27 +04:00
//// User{Id: 111, Name: "Jinzhu", Age: 20}
2013-10-30 11:33:34 +04:00
2013-11-02 17:02:54 +04:00
### FirstOrInit With Assign
Assign's arguments would be used to set the struct even record found, but won't be used for search
```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
//// User{Name: "non_existing", Age: 20}
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)
//// User{Id: 111, Name: "Jinzhu", Age: 30}
```
## FirstOrCreate
Try to load the first record, if fails, initialize struct with search conditions and save it
```go
db.FirstOrCreate(&user, User{Name: "non_existing"})
//// User{Id: 112, Name: "non_existing"}
2013-10-29 16:32:27 +04:00
db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
2013-11-02 17:02:54 +04:00
//// User{Id: 111, Name: "Jinzhu"}
db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30})
2013-10-29 16:32:27 +04:00
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
2013-11-02 17:02:54 +04:00
```
### FirstOrCreate With Attrs
2013-10-29 16:32:27 +04:00
2013-11-02 17:02:54 +04:00
Attr's arguments would be used to initialize struct if not record found, but won't be used for search
```go
2013-10-30 11:33:34 +04:00
// FirstOrCreate With Attrs
2013-11-02 17:02:54 +04:00
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'non_existing';
//// User{Id: 112, Name: "non_existing", Age: 20}
2013-10-31 05:34:27 +04:00
2013-11-02 17:02:54 +04:00
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
//// User{Id: 111, Name: "Jinzhu", Age: 20}
```
### FirstOrCreate With Assign
2013-10-31 05:34:27 +04:00
2013-11-02 17:02:54 +04:00
Assign's arguments would be used to initialize the struct if not record found,
If any record found, will assign those values to the record, and save it back to database.
2013-10-31 05:34:27 +04:00
2013-11-02 17:02:54 +04:00
```go
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
//// user -> User{Id: 112, Name: "non_existing", Age: 20}
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
//// UPDATE users SET age=30 WHERE id = 111;
//// User{Id: 111, Name: "Jinzhu", Age: 30}
```
### SELECT
```go
2013-10-30 11:33:34 +04:00
//// user -> User{Id: 111, Name: "Jinzhu", Age: 18}
//// You must noticed that the Attrs is similar to FirstOrInit with Attrs, yes?
2013-10-27 17:37:31 +04:00
// Select
db.Select("name").Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select name from users;
2013-10-27 17:37:31 +04:00
// Order
db.Order("age desc, name").Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select * from users order by age desc, name;
2013-10-27 18:18:06 +04:00
db.Order("age desc").Order("name").Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select * from users order by age desc, name;
2013-10-28 05:18:34 +04:00
// ReOrder
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
//// users1 -> select * from users order by age desc;
//// users2 -> select * from users order by age;
2013-10-27 17:37:31 +04:00
// Limit
db.Limit(3).Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select * from users limit 3;
db.Limit(10).Find(&users1).Limit(20).Find(&users2).Limit(-1).Find(&users3)
//// users1 -> select * from users limit 10;
//// users2 -> select * from users limit 20;
//// users3 -> select * from users;
2013-10-27 17:37:31 +04:00
// Offset
2013-10-28 04:08:45 +04:00
//// select * from users offset 3;
2013-10-27 17:37:31 +04:00
db.Offset(3).Find(&users)
2013-10-28 05:05:44 +04:00
db.Offset(10).Find(&users1).Offset(20).Find(&users2).Offset(-1).Find(&users3)
//// user1 -> select * from users offset 10;
//// user2 -> select * from users offset 20;
//// user3 -> select * from users;
2013-10-27 17:37:31 +04:00
// Or
2013-10-27 18:18:06 +04:00
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select * from users where role = 'admin' or role = 'super_admin';
2013-10-27 17:37:31 +04:00
// Count
2013-10-27 18:18:06 +04:00
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
2013-10-28 05:05:44 +04:00
//// users -> select * from users where name = 'jinzhu' or name = 'jinzhu 2';
//// count -> select count(*) from users where name = 'jinzhu' or name = 'jinzhu 2';
2013-10-27 18:18:06 +04:00
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
2013-10-27 17:37:31 +04:00
// CreatedAt (auto insert current time on create)
2013-10-27 18:18:06 +04:00
If your struct has field CreatedAt,
it will be filled with the current time when insert into database
2013-10-27 17:37:31 +04:00
// UpdatedAt (auto update the time on save)
2013-10-27 18:18:06 +04:00
If your struct has field UpdatedAt,
it will be filled with the current time when update it
2013-10-27 17:37:31 +04:00
// Callbacks
2013-10-27 18:18:06 +04:00
Below callbacks are defined now:
`BeforeCreate`, `BeforeUpdate`, `BeforeSave`, `AfterCreate`, `AfterUpdate`, `AfterSave`
`BeforeDelete`, `AfterDelete`
2013-10-27 17:37:31 +04:00
Callbacks is a function defined to a model, if the function return error, will prevent the database operations.
2013-10-28 16:27:25 +04:00
func (u *User) BeforeUpdate() (err error) {
2013-11-02 17:02:54 +04:00
if u.readonly() {
err = errors.New("Read Only User")
}
return
2013-10-28 16:27:25 +04:00
}
2013-10-27 18:18:06 +04:00
// Pluck (get users's age as map)
2013-10-27 17:37:31 +04:00
var ages []int64
2013-10-28 05:05:44 +04:00
db.Find(&users).Pluck("age", &ages)
//// ages -> select age from users;
2013-10-27 18:18:06 +04:00
var names []string
db.Model(&User{}).Pluck("name", &names)
2013-10-28 05:05:44 +04:00
//// names -> select name from users;
2013-10-27 17:37:31 +04:00
// Query Chains
db.Where("name <> ?", "jinzhu").Where("age >= ? and role <> ?", 20, "admin").Find(&users)
2013-10-28 05:05:44 +04:00
//// users -> select * from users where name <> 'jinzhu' andd age >= 20 and role <> 'admin';
2013-10-27 17:37:31 +04:00
// Create Table with struct
db.CreateTable(&User{})
2013-11-01 11:01:39 +04:00
// Drop Table
db.DropTable(&User{})
2013-10-28 16:27:25 +04:00
// Specify Table Name
db.Table("deleted_users").CreateTable(&User{})
db.Table("users").Pluck("age", &ages)
//// ages -> select age from users;
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// deleted_users -> select * from deleted_users;
db.Table("deleted_users").Find(&deleted_user)
//// deleted_user -> select * from deleted_users limit 1;
2013-10-28 17:52:22 +04:00
// Update
db.Table("users").Where(10).Update("name", "hello")
//// update users set name='hello' where id = 10;
db.Table("users").Update("name", "hello")
//// update users set name='hello';
// Updates
db.Table("users").Where(10).Updates(map[string]interface{}{"name": "hello", "age": 18})
//// update users set name='hello', age=18 where id = 10;
db.Table("users").Updates(map[string]interface{}{"name": "hello", "age": 18})
//// update users set name='hello', age=18;
db.Find(&users).Updates(User{Name: "hello", Age: 18})
//// update users set name='hello', age=18;
db.First(&user, 20).Updates(User{Name: "hello", Age: 18})
//// update users set name='hello', age=18 where id = 20;
//// object user's value would be reflected by the Updates also,
//// so you don't need to refetch the user from database
2013-10-28 17:52:22 +04:00
// Soft Delete
// For those struct have DeletedAt field, they will get soft delete ability automatically!
type Order struct {
2013-11-02 17:02:54 +04:00
Id int64
Amount int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
order := order{Id:10}
db.Delete(&order)
//// UPDATE orders SET deleted_at="2013-10-29 10:23" WHERE id = 10;
db.Where("amount = ?", 0).Delete(&Order{})
//// UPDATE orders SET deleted_at="2013-10-29 10:23" WHERE amount = 0;
db.Where("amount = 100").Find(&order)
//// order -> select * from orders where amount = 100 and (deleted_at is null and deleted_at <= '0001-01-02');
// And you are possible to query soft deleted orders with Unscoped method
db.Unscoped().Where("amount = 100").Find(&order)
//// order -> select * from orders where amount = 100;
// Of course, you could permanently delete a record with Unscoped
db.Unscoped().Delete(&order)
// DELETE from orders where id=10;
2013-10-27 17:37:31 +04:00
// Run Raw SQL
db.Exec("drop table users;")
2013-10-28 07:24:51 +04:00
// Error Handling
query := db.Where("name = ?", "jinzhu").First(&user)
query := db.First(&user).Limit(10).Find(&users)
//// query.Error -> the last error happened
//// query.Errors -> all errors happened
//// If an error happened, gorm will stop do insert, update, delete operations
2013-10-27 17:37:31 +04:00
```
2013-10-28 04:08:45 +04:00
## Advanced Usage With Query Chain
2013-10-27 18:18:06 +04:00
```go
// Already excited about the basic usage? Let's see some magic!
db.First(&first_article).Count(&total_count).Limit(10).Find(&first_page_articles).Offset(10).Find(&second_page_articles)
2013-10-28 05:05:44 +04:00
//// first_article -> select * from articles limit 1
//// total_count -> select count(*) from articles
//// first_page_articles -> select * from articles limit 10
//// second_page_articles -> select * from articles limit 10 offset 10
2013-10-27 18:18:06 +04:00
db.Where("created_at > ?", "2013/10/10").Find(&cancelled_orders, "state = ?", "cancelled").Find(&shipped_orders, "state = ?", "shipped")
2013-10-28 05:05:44 +04:00
//// cancelled_orders -> select * from orders where created_at > '2013/10/10' and state = 'cancelled'
//// shipped_orders -> select * from orders where created_at > '2013/10/10' and state = 'shipped'
2013-10-27 18:18:06 +04:00
db.Model(&Order{}).Where("amount > ?", 10000).Pluck("user_id", &paid_user_ids)
2013-10-28 05:05:44 +04:00
//// paid_user_ids -> select user_id from orders where amount > 10000
2013-10-27 18:18:06 +04:00
db.Where("user_id = ?", paid_user_ids).Find(&:paid_users)
2013-10-28 05:05:44 +04:00
//// paid_users -> select * from users where user_id in (10, 20, 99)
2013-10-27 18:18:06 +04:00
db.Where("product_name = ?", "fancy_product").Find(&orders).Find(&shopping_cart)
2013-10-28 05:05:44 +04:00
//// orders -> select * from orders where product_name = 'fancy_product'
//// shopping_cart -> select * from carts where product_name = 'fancy_product'
2013-10-28 04:08:45 +04:00
// Do you noticed the search table is different for above query, yay
2013-10-27 18:18:06 +04:00
2013-10-28 16:27:25 +04:00
db.Where("mail_type = ?", "TEXT").Find(&users1).Table("deleted_users").First(&user2)
//// users1 -> select * from users where mail_type = 'TEXT';
//// users2 -> select * from deleted_users where mail_type = 'TEXT';
2013-10-30 11:33:34 +04:00
db.Where("email = ?", "x@example.org"').Attrs(User{FromIp: "111.111.111.111"}).FirstOrCreate(&user)
//// user -> select * from users where email = 'x@example.org'
//// (if no record found) -> INSERT INTO "users" (email,from_ip) VALUES ("x@example.org", "111.111.111.111")
2013-10-27 18:18:06 +04:00
// Open your mind, add more cool examples
```
2013-10-26 11:18:44 +04:00
## TODO
2013-10-29 03:39:26 +04:00
* Index, Unique, Valiations
* Auto Migration
2013-10-26 11:18:44 +04:00
* SQL Log
2013-10-28 04:08:45 +04:00
* SQL Query with goroutines
* Only tested with postgres, confirm works with other database adaptors
2013-10-25 12:24:29 +04:00
2013-10-26 11:18:44 +04:00
# Author
2013-10-25 12:24:29 +04:00
2013-10-26 11:18:44 +04:00
**Jinzhu**
2013-10-25 12:24:29 +04:00
2013-10-26 11:18:44 +04:00
* <http://github.com/jinzhu>
* <wosmvp@gmail.com>
* <http://twitter.com/zhangjinzhu>