From a135087af4721adaa3c3325f3baacf0c2348501e Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Sun, 3 Nov 2013 10:09:56 +0800 Subject: [PATCH] Better README --- README.md | 429 +++++++++++++++++++++++++++++++-------------------- gorm_test.go | 17 +- 2 files changed, 272 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 96cbc91b..5b793fe7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Yet Another ORM library for Go, aims for developer friendly * Prevent SQL Injection * Goroutines friendly * Database Pool -* Convention Over Configuration (CoC) +* Convention Over Configuration ## Basic Usage @@ -55,9 +55,9 @@ type User struct { // TableName: `users`, gorm will pluralize struc 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 + BillingAddressId int64 // Embedded struct BillingAddress's foreign key ShippingAddress Address // Embedded struct - ShippingAddressId int64 // Embedded struct's foreign key + ShippingAddressId int64 // Embedded struct ShippingAddress's foreign key } type Email struct { // TableName: `emails` @@ -92,36 +92,26 @@ user := User{Name: "jinzhu", Age: 18, Birthday: time.Now()} db.Save(&user) ``` -## Update - -```go -user.Name = "jinzhu 2" -db.Save(&user) -``` - -## Delete - -```go -db.Delete(&user) -``` - ## Query ```go // Get the first record db.First(&user) -//// SELECT * FROM USERS LIMIT 1; +//// SELECT * FROM users LIMIT 1; +// Search table `users` are guessed from the out struct's name. +// You are possible to specify the table name with Model() if no out struct for some methods like Pluck() +// Or set table name with Table(), if so, it will ignore the out struct's type even have it. more details later. // Get All records db.Find(&users) -//// SELECT * FROM USERS; +//// SELECT * FROM users; // Using a Primary Key db.First(&user, 10) -//// SELECT * FROM USERS WHERE id = 10; +//// SELECT * FROM users WHERE id = 10; ``` -### Where (SQL like condition) +### Query With Where (SQL like condition) ```go // Get the first matched record @@ -139,10 +129,6 @@ db.Where("name <> ?", "jinzhu").Find(&users) db.Where("name in (?)", []string["jinzhu", "jinzhu 2"]).Find(&users) //// 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) //// SELECT * FROM users WHERE name LIKE "%jin%"; @@ -152,7 +138,7 @@ db.Where("name = ? and age >= ?", "jinzhu", "22").Find(&users) //// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22; ``` -### Where (Struct & Map) +### Query With Where (Struct & Map) ```go // Search with struct @@ -162,12 +148,16 @@ db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) // Search with map db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) //// SELECT * FROM users WHERE name = "jinzhu" AND age = 20; + +// IN For Primary Key +db.Where([]int64{20, 21, 22}).Find(&users) +//// SELECT * FROM users WHERE id IN (20, 21, 22); ``` -### Not +### Query With Not ```go -// Not Equal +// Attribute Not Equal db.Not("name", "jinzhu").First(&user) //// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1; @@ -191,7 +181,7 @@ db.Not(User{Name: "jinzhu"}).First(&user) //// SELECT * FROM users WHERE name <> "jinzhu"; ``` -### Inline Search Condition +### Inline Search ```go // Find with primary key @@ -215,6 +205,117 @@ db.Find(&users, map[string]interface{}{"age": 20}) //// SELECT * FROM users WHERE age = 20; ``` +### Query With Or + +``` +db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users) +//// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin'; + +// Or With Struct +db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users) +//// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; + +// Or With Map +db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users) +``` + +### Query Chains + +Gorm has a chainable API, so you could write query in chain + +```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) +``` + +## Update + +### Update an existing struct + +```go +user.Name = "jinzhu 2" +user.Age = 100 +db.Save(&user) +//// UPDATE users SET name='jinzhu 2', age=100 WHERE id=111; +``` + +### Update one attribute with `Update` + +``` +// Update an existing struct's name if name is different +db.Model(&user).Update("name", "hello") +//// UPDATE users SET name='hello' WHERE id=111; + +// Find out a struct, and update it if name is different +db.First(&user, 111).Update("name", "hello") +//// SELECT * FROM users LIMIT 1; +//// UPDATE users SET name='hello' WHERE id=111; + +// Update a record +db.Table("users").Where(10).Update("name", "hello") +//// UPDATE users SET name='hello' WHERE id = 10; +``` + +### Update multiple attributes with `Updates` + +``` +// Update an existing record if have any different attributes +db.Model(&user).Updates(User{Name: "hello", Age: 18}) +//// UPDATE users SET name='hello', age=18 WHERE id = 111; + +// Update with Map +db.Table("users").Where(10).Updates(map[string]interface{}{"name": "hello", "age": 18}) +//// UPDATE users SET name='hello', age=18 WHERE id = 10; + +// Update with Struct +db.Model(User{}).Updates(User{Name: "hello", Age: 18}) +//// UPDATE users SET name='hello', age=18; +``` + +## Delete + +### Delete an existing struct + +```go +db.Delete(&email) +// DELETE from emails where id=10; +``` + +### Batch Delete with search + +```go +db.Where("email LIKE ?", "%jinzhu%").Delete(Email{}) +// DELETE from emails where email LIKE "%jinhu%"; +``` + +### Soft Delete + +If a struct have DeletedAt field, it will get soft delete ability automatically! +For those don't have the filed, will be deleted from database permanently + +```go +db.Delete(&user) +//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; + +// Batch delete when search +db.Where("age = ?", 20).Delete(&User{}) +//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; + +// For structs have DeletedAt field, when do query, will ignore deleted records by default +db.Where("age = 20").Find(&user) +//// SELECT * FROM users WHERE age = 100 AND (deleted_at IS NULL AND deleted_at <= '0001-01-02'); + +// Find out all records including those deleted with Unscoped +db.Unscoped().Where("age = 20").Find(&users) +//// SELECT * FROM users WHERE age = 20; + +// Permanently delete a record with Unscoped +db.Unscoped().Delete(&order) +// DELETE FROM orders WHERE id=10; +``` + ## FirstOrInit Try to load the first record, if fails, initialize struct with search conditions. @@ -233,24 +334,24 @@ db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"}) ### FirstOrInit With Attrs -Attr's arguments would be used to initialize struct if not record found, but won't be used for search +Attr's arguments would be used to initialize struct if no 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 +// Above code could be simplified if has only one attribute db.Where(User{Name: "noexisting_user"}).Attrs("age", 20).FirstOrInit(&user) -// If record found, Attrs would be ignored +// If a record found, Attrs would be just ignored db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user) //// SELECT * FROM USERS WHERE name = jinzhu'; -//// User{Id: 111, Name: "Jinzhu", Age: 20} +//// User{Id: 111, Name: "Jinzhu", Age: 20} ### FirstOrInit With Assign -Assign's arguments would be used to set the struct even record found, but won't be used for search +Assign's arguments would be used to set the struct even a record found, but won't be used for search ```go db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user) @@ -272,21 +373,20 @@ db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user) //// User{Id: 111, Name: "Jinzhu"} db.FirstOrCreate(&user, map[string]interface{}{"name": "jinzhu", "age": 30}) -//// user -> User{Id: 111, Name: "Jinzhu", Age: 20} +//// 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 +Attr's arguments would be used to initialize struct if no record found, but won't be used for search ```go -// FirstOrCreate With Attrs 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} -db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user) -//// User{Id: 111, Name: "Jinzhu", Age: 20} +db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user) +//// User{Id: 111, Name: "jinzhu", Age: 20} ``` ### FirstOrCreate With Assign @@ -298,192 +398,179 @@ If any record found, will assign those values to the record, and save it back to 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) +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{Id: 111, Name: "Jinzhu", Age: 30} +//// User{Id: 111, Name: "jinzhu", Age: 30} ``` -### SELECT +## Select ```go -//// user -> User{Id: 111, Name: "Jinzhu", Age: 18} -//// You must noticed that the Attrs is similar to FirstOrInit with Attrs, yes? +db.Select("name, age").Find(&users) +//// SELECT name, age FROM users; +``` -// Select -db.Select("name").Find(&users) -//// users -> select name from users; +## Order -// Order +```go db.Order("age desc, name").Find(&users) -//// users -> select * from users order by age desc, name; +//// SELECT * FROM users ORDER BY age desc, name; + db.Order("age desc").Order("name").Find(&users) -//// users -> select * from users order by age desc, name; +//// SELECT * FROM users ORDER BY age desc, name; + // 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; +//// SELECT * FROM users ORDER BY age desc; (users1) +//// SELECT * FROM users ORDER BY age; (users2) +``` -// Limit +## Limit + +```go db.Limit(3).Find(&users) -//// 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; +//// SELECT * FROM users LIMIT 3; -// Offset -//// select * from users offset 3; +// Cleanup limit 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) -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; +//// SELECT * FROM users OFFSET 3; -// Or -db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users) -//// users -> select * from users where role = 'admin' or role = 'super_admin'; +// Cleanup offset with -1 +db.Offset(10).Find(&users1).Offset(-1).Find(&users2) +//// SELECT * FROM users OFFSET 10; (users1) +//// SELECT * FROM users; (users2) +``` -// Count +## Count + +```go db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count) -//// users -> select * from users where name = 'jinzhu' or name = 'jinzhu 2'; -//// count -> select count(*) from users where name = 'jinzhu' or name = 'jinzhu 2'; -db.Model(&User{}).Where("name = ?", "jinzhu").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) -// CreatedAt (auto insert current time on create) -If your struct has field CreatedAt, -it will be filled with the current time when insert into database +// Set table name with Model +db.Model(User{}).Where("name = ?", "jinzhu").Count(&count) +//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count) -// UpdatedAt (auto update the time on save) -If your struct has field UpdatedAt, -it will be filled with the current time when update it +// Set table name with Table +db.Table("deleted_users").Count(&count) +//// SELECT count(*) FROM deleted_users; +``` -// Callbacks -Below callbacks are defined now: +## Pluck -`BeforeCreate`, `BeforeUpdate`, `BeforeSave`, `AfterCreate`, `AfterUpdate`, `AfterSave` -`BeforeDelete`, `AfterDelete` +Get struct's attribute as map -Callbacks is a function defined to a model, if the function return error, will prevent the database operations. - -func (u *User) BeforeUpdate() (err error) { -if u.readonly() { -err = errors.New("Read Only User") -} -return -} - -// Pluck (get users's age as map) +```go var ages []int64 db.Find(&users).Pluck("age", &ages) -//// ages -> select age from users; + +// Set Table With Model var names []string db.Model(&User{}).Pluck("name", &names) -//// names -> select name from users; +//// SELECT name FROM users; -// Query Chains -db.Where("name <> ?", "jinzhu").Where("age >= ? and role <> ?", 20, "admin").Find(&users) -//// users -> select * from users where name <> 'jinzhu' andd age >= 20 and role <> 'admin'; +// Set Table With Table +db.Table("deleted_users").Pluck("name", &names) +//// SELECT name FROM deleted_users; +``` -// Create Table with struct -db.CreateTable(&User{}) +## Callbacks -// Drop Table -db.DropTable(&User{}) +Callback is a function defined to a struct, the function would be run when reflect a struct to database. +If the function return an error, will prevent following operations. (for example, stop inserting, updating) -// Specify Table Name +Those callbacks are defined now: + +`BeforeCreate`, `AfterCreate` +`BeforeUpdate`, `AfterUpdate` +`BeforeSave`, `AfterSave` +`BeforeDelete`, `AfterDelete` + +```go +func (u *User) BeforeUpdate() (err error) { + if u.readonly() { + err = errors.New("Read Only User") + } + return +} +``` + +## Specify Table Name + +``` +// When Create Table from struct db.Table("deleted_users").CreateTable(&User{}) + +// When Pluck db.Table("users").Pluck("age", &ages) -//// ages -> select age from users; +//// SELECT age FROM users; + +// When Query 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; +//// SELECT * FROM deleted_users; -// 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'; +// When Delete +db.Table("deleted_users").Where("name = ?", "jinzhu").Delete() +//// DELETE FROM deleted_users WHERE name = 'jinzhu'; +``` -// 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 +## Run Raw SQl -// Soft Delete -// For those struct have DeletedAt field, they will get soft delete ability automatically! -type Order struct { -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; - -// Run Raw SQL +```go db.Exec("drop table users;") +``` -// Error Handling +## Error Handling + +```go 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 +//// query.Error keep the latest error happened +//// query.Errors keep all errors happened +//// If an error happened, gorm will stop do query, insert, update, delete + +// I often use below code to do error handling in real applicatoins +err = db.Where("name = ?", "jinzhu").First(&user).Error ``` ## Advanced Usage With Query Chain +Already excited about above usage? Let's see some magic! + ```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) -//// 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 +//// SELECT * FROM articles LIMIT 1; (first_article) +//// SELECT count(*) FROM articles; (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") -//// 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' +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) -db.Model(&Order{}).Where("amount > ?", 10000).Pluck("user_id", &paid_user_ids) -//// paid_user_ids -> select user_id from orders where amount > 10000 -db.Where("user_id = ?", paid_user_ids).Find(&:paid_users) -//// paid_users -> select * from users where user_id in (10, 20, 99) - -db.Where("product_name = ?", "fancy_product").Find(&orders).Find(&shopping_cart) -//// orders -> select * from orders where product_name = 'fancy_product' -//// shopping_cart -> select * from carts where product_name = 'fancy_product' -// Do you noticed the search table is different for above query, yay +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) +// Do you noticed the table is different? 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'; +//// SELECT * FROM users WHERE mail_type = 'TEXT'; (users1) +//// SELECT * FROM deleted_users WHERE mail_type = 'TEXT'; (users2) 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") +//// SELECT * FROM users WHERE email = 'x@example.org'; +//// INSERT INTO "users" (email,from_ip) VALUES ("x@example.org", "111.111.111.111") (if no record found) // Open your mind, add more cool examples ``` @@ -497,7 +584,7 @@ db.Where("email = ?", "x@example.org"').Attrs(User{FromIp: "111.111.111.111"}).F # Author -**Jinzhu** +**jinzhu** * * diff --git a/gorm_test.go b/gorm_test.go index 7e76c722..c6a46164 100644 --- a/gorm_test.go +++ b/gorm_test.go @@ -1095,10 +1095,21 @@ func TestT(t *testing.T) { db.Save(&user) var user2 User - db.Where("name in (?)", []string{"1,2"}).First(&user2).Update("name", "hhh") + debug("asdsd") + db.Where("name in (?)", []string{"1"}).First(&user2) + + debug("aaadsd") debug(user2) + db.Model(&user2).Updates(User{Name: "lala", Age: 10}) + debug("aals") + //// UPDATE users SET name='hello' WHERE id=111; var users []User - db.Find(&users, User{Age: 20}) - debug(users) + // db.Where("name = '3'").Or(User{Name: "2"}).Find(&users) + db.Where("name = '3'").Or(map[string]interface{}{"name": "2"}).Find(&users) + var count int64 + db.Model(User{}).Where("name = ?", "jinzhu").Count(&count) + db.Table("users").Count(&count) + debug(count) + }