Update README

This commit is contained in:
Jinzhu 2013-11-02 21:02:54 +08:00
parent aa352d405b
commit b4981259de
2 changed files with 311 additions and 137 deletions

378
README.md
View File

@ -6,167 +6,306 @@ Yet Another ORM library for Go, aims for developer friendly
* CURD * CURD
* Chainable API * Chainable API
* Embedded structs support
* Before/After Create/Save/Update/Delete Callbacks * Before/After Create/Save/Update/Delete Callbacks
* Order/Select/Limit/Offset Support
* Update, Updates Like Rails's update_attribute, update_attributes * Update, Updates Like Rails's update_attribute, update_attributes
* FirstOrInit, FirstOrCreate Like Rails's first_or_initialize, first_or_create * FirstOrInit, FirstOrCreate Like Rails's first_or_initialize, first_or_create
* Dynamically set table name when search, update, delete... * Order/Select/Limit/Offset Support
* Automatically CreatedAt, UpdatedAt * Automatically CreatedAt, UpdatedAt
* Soft Delete * Soft Delete
* Create table from struct * Create/Drop table from struct
* Dynamically set table name when search, create, update, delete...
* Prevent SQL Injection * Prevent SQL Injection
* Goroutines friendly * Goroutines friendly
* Database Pool * Database Pool
* Convention Over Configuration (CoC)
## Basic Usage ## Basic Usage
```go ## Opening a Database
db, _ = Open("postgres", "user=gorm dbname=gorm sslmode=disable")
type User struct { ```go
Id int64 db, err = Open("postgres", "user=gorm dbname=gorm sslmode=disable")
Age int64
Birthday time.Time // Gorm is goroutines friendly, so you can create a global variable to keep the connection and use it everywhere
Name string
CreatedAt time.Time var DB gorm.DB
UpdatedAt time.Time
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 database pool // Set the maximum idle database connections
db.SetPool(10) db.SetPool(100)
```
// Create ## Conventions
user = User{Name: "jinzhu", Age: 18, Birthday: time.Now()}
```go
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
}
type Email struct { // TableName: `emails`
Id int64
UserId int64 // Foreign key for above embedded structs
Email string
Subscribed bool
}
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()}
db.Save(&user) db.Save(&user)
```
// Update ## Update
```go
user.Name = "jinzhu 2" user.Name = "jinzhu 2"
db.Save(&user) db.Save(&user)
```
// Delete ## Delete
```go
db.Delete(&user) db.Delete(&user)
```
// Get First matched record ## Query
```go
// Get the first record
db.First(&user)
//// SELECT * FROM USERS LIMIT 1;
// 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
db.Where("name = ?", "jinzhu").First(&user) db.Where("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;
// Get All matched records // Get all matched records
db.Where("name = ?", "jinzhu").Find(&users) db.Where("name = ?", "jinzhu").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu';
// Advanced Where Usage
db.Where("name <> ?", "jinzhu").Find(&users) db.Where("name <> ?", "jinzhu").Find(&users)
//// users -> select * from users name <> 'jinzhu'; //// SELECT * FROM users WHERE name <> 'jinzhu';
db.Where(20).First(&user)
//// users -> select * from users where id = 20; // IN
db.Where([]int64{20, 21, 22}).Find(&user)
//// users -> select * from users where id in (20, 21, 22);
db.Where("name = ? and age >= ?", "jinzhu", "22").Find(&users)
//// users -> select * from users name = 'jinzhu' and age >= 22;
db.Where("name in (?)", []string["jinzhu", "jinzhu 2"]).Find(&users) db.Where("name in (?)", []string["jinzhu", "jinzhu 2"]).Find(&users)
//// users -> select * from users name in ('jinzhu', 'jinzhu 2'); //// 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
db.Where("name LIKE ?", "%jin%").Find(&users) db.Where("name LIKE ?", "%jin%").Find(&users)
//// users -> select * from users name LIKE "%jinzhu%"; //// 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
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
//// user -> select * from users name = "jinzhu" and age = 20 limit 1; //// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).First(&user)
//// user -> select * from users name = "jinzhu" and age = 20 limit 1;
db.Where("birthday < ?", time.Now()).Find(&users)
// Not // Search with map
db.Not([]int64{1,2,3}).First(&user) db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// user -> select * from users where id NOT IN (1,2,3) //// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
db.Not([]int64{}).First(&user) ```
//// user -> select * from users;
### Not
```go
// Not Equal
db.Not("name", "jinzhu").First(&user) db.Not("name", "jinzhu").First(&user)
//// user -> select * from users where name <> "jinzhu" //// 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 for Primary Key
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;
// Normal SQL
db.Not("name = ?", "jinzhu").First(&user) db.Not("name = ?", "jinzhu").First(&user)
//// user -> select * from users where NOT(name = "jinzhu") //// SELECT * FROM users WHERE NOT(name = "jinzhu");
db.Not("name <> ?", "jinzhu").First(&user)
//// user -> select * from users where NOT(name <> "jinzhu") // Not With Struct
db.Not("name", []string{"jinzhu", "jinzhu 2"}).First(&user)
//// user -> select * from users where name NOT IN ("jinzhu", "jinzhu 2")
db.Not(User{Name: "jinzhu"}).First(&user) db.Not(User{Name: "jinzhu"}).First(&user)
//// user -> select * from users where name <> "jinzhu"; //// SELECT * FROM users WHERE name <> "jinzhu";
```
// Inline search condition ### Inline Search Condition
```go
// Find with primary key
db.First(&user, 23) db.First(&user, 23)
//// user -> select * from users where id = 23 limit 1; //// SELECT * FROM users WHERE id = 23 LIMIT 1;
db.First(&user, "name = ?", "jinzhu")
//// user -> select * from users where name = "jinzhu" limit 1; // Normal SQL
db.Find(&users, "name = ?", "jinzhu") db.Find(&user, "name = ?", "jinzhu")
//// users -> select * from users where name = "jinzhu"; //// SELECT * FROM users WHERE name = "jinzhu";
// Multiple Conditions
db.Find(&users, "name <> ? and age > ?", "jinzhu", 20) db.Find(&users, "name <> ? and age > ?", "jinzhu", 20)
//// users -> select * from users where name <> "jinzhu" and age > 20; //// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
db.Find(&users, &User{Age: 20})
//// users -> select * from users where age = 20; // Inline Search With Struct
db.Find(&users, User{Age: 20})
//// SELECT * FROM users WHERE age = 20;
// Inline Search With Map
db.Find(&users, map[string]interface{}{"age": 20}) db.Find(&users, map[string]interface{}{"age": 20})
//// users -> select * from users where age = 20; //// 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"}
// FirstOrInit
db.FirstOrInit(&user, User{Name: "noexisting_user"})
//// user -> User{Name: "noexisting_user"}
db.Where(User{Name: "Jinzhu"}).FirstOrInit(&user) db.Where(User{Name: "Jinzhu"}).FirstOrInit(&user)
//// user -> User{Id: 111, Name: "Jinzhu"} //// User{Id: 111, Name: "Jinzhu", Age: 20}
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu", "age": 20})
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
// FirstOrInit With Attrs db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
db.Where(User{Name: "noexisting_user"}).Attrs(User{Age: 20}).FirstOrInit(&user) //// User{Id: 111, Name: "Jinzhu", Age: 20}
//// user -> select * from users where name = 'noexisting_user'; ```
//// If no record found, will assign the attrs to user, so user become:
//// User{Name: "noexisting_user", 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) db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user)
// Same as above
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
//// user -> select * from users where name = 'jinzhu';
//// If found the user, will ingore the attrs:
//// User{Id: 111, Name: "Jinzhu", Age: 18}
// FirstOrInit With Assign // If record found, Attrs would be ignored
db.Where(User{Name: "noexisting_user"}).Assign(User{Age: 20}).FirstOrInit(&user) db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
//// user -> select * from users where name = 'noexisting_user'; //// SELECT * FROM USERS WHERE name = jinzhu';
//// If no record found, will assign the value to user, so user become:
//// User{Name: "noexisting_user", Age: 20} (same as FirstOrInit With Attrs)
db.Where(User{Name: "noexisting_user"}).Assign("age", 20).FirstOrInit(&user)
// Same as above
//// user -> User{Name: "noexisting_user", Age: 20}
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
//// user -> select * from users where name = 'jinzhu';
//// If found the user, will assign the value to user, so user become: (different with FirstOrInit With Attrs)
//// User{Id: 111, Name: "Jinzhu", Age: 20} //// User{Id: 111, Name: "Jinzhu", Age: 20}
// FirstOrCreate ### FirstOrInit With Assign
db.FirstOrCreate(&user, User{Name: "noexisting_user"})
//// user -> User{Id: 112, Name: "noexisting_user"} 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"}
db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user) db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
//// user -> User{Id: 111, Name: "Jinzhu"} //// User{Id: 111, Name: "Jinzhu"}
db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 20})
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30})
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
```
### FirstOrCreate With Attrs
Attr's arguments would be used to initialize struct if not record found, but won't be used for search
```go
// FirstOrCreate With Attrs // FirstOrCreate With Attrs
db.Where(User{Name: "noexisting_user"}).Attrs(User{Age: 20}).FirstOrCreate(&user) db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
//// user -> select * from users where name = 'noexisting_user'; //// SELECT * FROM users WHERE name = 'non_existing';
//// If not record found, will assing the attrs to the user first, then create it //// User{Id: 112, Name: "non_existing", Age: 20}
//// Same as db.Where(User{Name: "noexisting_user"}).FirstOrCreate(&user).Update("age": 20), but one less sql
db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrCreate(&user)
// Save as above
//// user -> User{Id: 112, Name: "noexisting_user", Age: 20}
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
//// user -> select * from users where name = 'jinzhu';
//// If found any record, will ignore the attrs
//// user -> User{Id: 111, Name: "Jinzhu", Age: 18}
// FirstOrCreate With Assign db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
db.Where(User{Name: "noexisting_user"}).Assign(User{Age: 20}).FirstOrCreate(&user) //// User{Id: 111, Name: "Jinzhu", Age: 20}
//// user -> select * from users where name = 'noexisting_user'; ```
//// If not record found, will assing the value to the user first, then create it
//// user -> User{Id: 112, Name: "noexisting_user", Age: 20} (Same as FirstOrCreate With Attrs)
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
//// user -> select * from users where name = 'jinzhu';
//// If any record found, will assing the value to the user and update it
//// UPDATE users SET age=20 WHERE id = 111;
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
### FirstOrCreate With Assign
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.
```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
//// user -> User{Id: 111, Name: "Jinzhu", Age: 18} //// user -> User{Id: 111, Name: "Jinzhu", Age: 18}
//// You must noticed that the Attrs is similar to FirstOrInit with Attrs, yes? //// You must noticed that the Attrs is similar to FirstOrInit with Attrs, yes?
@ -227,10 +366,10 @@ Below callbacks are defined now:
Callbacks is a function defined to a model, if the function return error, will prevent the database operations. Callbacks is a function defined to a model, if the function return error, will prevent the database operations.
func (u *User) BeforeUpdate() (err error) { func (u *User) BeforeUpdate() (err error) {
if u.readonly() { if u.readonly() {
err = errors.New("Read Only User") err = errors.New("Read Only User")
} }
return return
} }
// Pluck (get users's age as map) // Pluck (get users's age as map)
@ -282,11 +421,11 @@ db.First(&user, 20).Updates(User{Name: "hello", Age: 18})
// Soft Delete // Soft Delete
// For those struct have DeletedAt field, they will get soft delete ability automatically! // For those struct have DeletedAt field, they will get soft delete ability automatically!
type Order struct { type Order struct {
Id int64 Id int64
Amount int64 Amount int64
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
DeletedAt time.Time DeletedAt time.Time
} }
order := order{Id:10} order := order{Id:10}
db.Delete(&order) db.Delete(&order)
@ -350,7 +489,6 @@ db.Where("email = ?", "x@example.org"').Attrs(User{FromIp: "111.111.111.111"}).F
``` ```
## TODO ## TODO
* SubStruct
* Index, Unique, Valiations * Index, Unique, Valiations
* Auto Migration * Auto Migration
* SQL Log * SQL Log

View File

@ -12,12 +12,32 @@ import (
) )
type User struct { type User struct {
Id int64 Id int64 // Id: Primary key
Age int64 Birthday time.Time // Time
Birthday time.Time Age int64
Name string Name string
CreatedAt time.Time CreatedAt time.Time // CreatedAt: Time of record is created, will be insert automatically
UpdatedAt time.Time 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
}
type Email struct {
Id int64
UserId int64 // Foreign key for above embedded structs
Email string
Subscribed bool
}
type Address struct {
Id int64
Address1 string
Address2 string
Post string
} }
type Product struct { type Product struct {
@ -1002,12 +1022,14 @@ type Category struct {
} }
type Post struct { type Post struct {
Id int64 Id int64
CategoryId int64 CategoryId int64
Title string MainCategoryId int64
Body string Title string
Comments []Comment Body string
Category Category Comments []Comment
Category Category
MainCategory Category
} }
type Comment struct { type Comment struct {
@ -1027,10 +1049,11 @@ func TestSubStruct(t *testing.T) {
db.CreateTable(Comment{}) db.CreateTable(Comment{})
post := Post{ post := Post{
Title: "post 1", Title: "post 1",
Body: "body 1", Body: "body 1",
Comments: []Comment{{Content: "Comment 1"}, {Content: "Comment 2"}}, Comments: []Comment{{Content: "Comment 1"}, {Content: "Comment 2"}},
Category: Category{Name: "Category 1"}, Category: Category{Name: "Category 1"},
MainCategory: Category{Name: "Main Category 1"},
} }
if err := db.Save(&post).Error; err != nil { if err := db.Save(&post).Error; err != nil {
@ -1043,7 +1066,7 @@ func TestSubStruct(t *testing.T) {
var p Post var p Post
db.First(&p, post.Id) db.First(&p, post.Id)
if post.CategoryId == 0 || p.CategoryId == 0 { if post.CategoryId == 0 || p.CategoryId == 0 || post.MainCategoryId == 0 || p.MainCategoryId == 0 {
t.Errorf("Category Id should exist") t.Errorf("Category Id should exist")
} }
@ -1066,3 +1089,16 @@ func TestSubStruct(t *testing.T) {
comment3 := Comment{Content: "Comment 3", Post: Post{Title: "Title 3", Body: "Body 3"}} comment3 := Comment{Content: "Comment 3", Post: Post{Title: "Title 3", Body: "Body 3"}}
db.Save(&comment3) db.Save(&comment3)
} }
func TestT(t *testing.T) {
user := User{Name: "jinzhu", Age: 18, Birthday: time.Now()}
db.Save(&user)
var user2 User
db.Where("name in (?)", []string{"1,2"}).First(&user2).Update("name", "hhh")
debug(user2)
var users []User
db.Find(&users, User{Age: 20})
debug(users)
}