From 1a2eef181a237048c29d421e9822598edce47264 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Sun, 17 Nov 2013 21:39:50 +0800 Subject: [PATCH] Add Method UpdateColumn, UpdateColumns --- README.md | 19 +++++++++++---- do.go | 65 +++++++++++++++++++++++++++++++++------------------- gorm_test.go | 27 +++++++++++++++++++++- main.go | 8 +++++++ model.go | 4 ++++ 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f11cdb69..ec5ce59f 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,7 @@ db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jin user.Name = "jinzhu 2" user.Age = 100 db.Save(&user) -//// UPDATE users SET name='jinzhu 2', age=100 WHERE id=111; +//// UPDATE users SET name='jinzhu 2', age=100, updated_at = '2013-11-17 21:34:10' WHERE id=111; ``` ### Update one attribute with `Update` @@ -328,12 +328,12 @@ db.Save(&user) ```go // Update existing user's name if it is changed db.Model(&user).Update("name", "hello") -//// UPDATE users SET name='hello' WHERE id=111; +//// UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111; // Find out a user, and update the name if it is changed db.First(&user, 111).Update("name", "hello") //// SELECT * FROM users LIMIT 1; -//// UPDATE users SET name='hello' WHERE id=111; +//// UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111; // Update name with search condiation and specified table name db.Table("users").Where(10).Update("name", "hello") @@ -345,7 +345,7 @@ db.Table("users").Where(10).Update("name", "hello") ```go // Update user's name and age if they are changed db.Model(&user).Updates(User{Name: "hello", Age: 18}) -//// UPDATE users SET name='hello', age=18 WHERE id = 111; +//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111; // Updates with Map db.Table("users").Where(10).Updates(map[string]interface{}{"name": "hello", "age": 18}) @@ -356,6 +356,16 @@ db.Model(User{}).Updates(User{Name: "hello", Age: 18}) //// UPDATE users SET name='hello', age=18; ``` +### Update attributes without callbacks + +```go +db.Model(&user).UpdateColumn("name", "hello") +//// UPDATE users SET name='hello' WHERE id = 111; + +db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18}) +//// UPDATE users SET name='hello', age=18 WHERE id = 111; +``` + ## Delete ### Delete an existing struct @@ -780,7 +790,6 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111 ``` ## TODO -* UpdateColumn/Columns * Scopes * Joins * Scan diff --git a/do.go b/do.go index 222e4ea1..1dbea5bf 100644 --- a/do.go +++ b/do.go @@ -199,42 +199,51 @@ func (s *Do) create() (i interface{}) { return } +func (s *Do) convertToMapInterface(values interface{}) map[string]interface{} { + attrs := map[string]interface{}{} + + switch value := values.(type) { + case map[string]interface{}: + attrs = value + case []interface{}: + for _, v := range value { + for key, value := range s.convertToMapInterface(v) { + attrs[key] = value + } + } + case interface{}: + m := &Model{data: values, do: s} + for _, field := range m.columnsHasValue("other") { + attrs[field.dbName] = field.Value + } + } + return attrs +} + func (s *Do) updateAttrs(values interface{}, ignore_protected_attrs ...bool) *Do { ignore_protected := len(ignore_protected_attrs) > 0 && ignore_protected_attrs[0] s.usingUpdate = true - switch value := values.(type) { - case map[string]interface{}: - if len(value) > 0 { - results, has_update := s.model.updatedColumnsAndValues(value, ignore_protected) - if len(results) > 0 { - s.update_attrs = results - } - s.hasUpdate = has_update + if maps := s.convertToMapInterface(values); len(maps) > 0 { + results, has_update := s.model.updatedColumnsAndValues(maps, ignore_protected) + if len(results) > 0 { + s.update_attrs = results } - case []interface{}: - for _, v := range value { - s.updateAttrs(v) - } - case interface{}: - m := &Model{data: values, do: s} - attrs := map[string]interface{}{} - for _, field := range m.columnsHasValue("other") { - attrs[field.dbName] = field.Value - } - s.updateAttrs(attrs) + s.hasUpdate = has_update } return s } -func (s *Do) prepareUpdateSql() { +func (s *Do) prepareUpdateSql(include_self bool) { var sqls []string for key, value := range s.update_attrs { sqls = append(sqls, fmt.Sprintf("%v = %v", key, s.addToVars(value))) } - for key, value := range s.model.columnsAndValues("update") { - sqls = append(sqls, fmt.Sprintf("%v = %v", key, s.addToVars(value))) + if include_self { + for key, value := range s.model.columnsAndValues("update") { + sqls = append(sqls, fmt.Sprintf("%v = %v", key, s.addToVars(value))) + } } s.sql = fmt.Sprintf( @@ -246,6 +255,16 @@ func (s *Do) prepareUpdateSql() { return } +func (s *Do) updateColumns(value interface{}) *Do { + s.update_attrs = s.convertToMapInterface(value) + s.prepareUpdateSql(false) + if !s.db.hasError() { + s.exec() + s.updateAttrs(s.update_attrs) + } + return s +} + func (s *Do) update() *Do { if s.usingUpdate && !s.hasUpdate { return s @@ -255,7 +274,7 @@ func (s *Do) update() *Do { s.model.callMethod("BeforeSave") s.saveBeforeAssociations() - s.prepareUpdateSql() + s.prepareUpdateSql(true) if !s.db.hasError() { s.exec() diff --git a/gorm_test.go b/gorm_test.go index b32e6ed6..1abf2e90 100644 --- a/gorm_test.go +++ b/gorm_test.go @@ -900,6 +900,31 @@ func TestUpdates(t *testing.T) { } } +func TestUpdateColumn(t *testing.T) { + product1 := Product{Code: "update_column 1", Price: 10} + product2 := Product{Code: "update_column 2", Price: 20} + db.Save(&product1).Save(&product2).UpdateColumn(map[string]interface{}{"code": "update_column 3", "price": 100}) + if product2.Code != "update_column 3" || product2.Price != 100 { + t.Errorf("product 2 should be updated with update column") + } + + var product3 Product + db.First(&product3, product1.Id) + if product3.Code != "update_column 1" || product3.Price != 10 { + t.Errorf("product 1 should not be updated") + } + + var product4, product5 Product + db.First(&product4, product2.Id) + updated_at1 := product4.UpdatedAt + + db.Model(Product{}).Where(product2.Id).UpdateColumn("code", "update_column_new") + db.First(&product5, product2.Id) + if updated_at1.Format(time.RFC3339Nano) != product5.UpdatedAt.Format(time.RFC3339Nano) { + t.Errorf("updated_at should not be updated with update column") + } +} + func TestSoftDelete(t *testing.T) { type Order struct { Id int64 @@ -1444,7 +1469,7 @@ func BenchmarkGorm(b *testing.B) { // Query db.First(&BigEmail{}, "email = ?", e) // Update - db.Model(&email).Update("email", "new-"+e) + db.Model(&email).UpdateColumn("email", "new-"+e) // Delete db.Delete(&email) } diff --git a/main.go b/main.go index 96419162..eec0fbd6 100644 --- a/main.go +++ b/main.go @@ -153,6 +153,14 @@ func (s *DB) Updates(values interface{}, ignore_protected_attrs ...bool) *DB { return s.clone().do(s.data).begin().updateAttrs(values, ignore_protected_attrs...).update().commit_or_rollback().db } +func (s *DB) UpdateColumn(attrs ...interface{}) *DB { + return s.UpdateColumns(toSearchableMap(attrs...), true) +} + +func (s *DB) UpdateColumns(values interface{}, ignore_protected_attrs ...bool) *DB { + return s.clone().do(s.data).begin().updateColumns(values).commit_or_rollback().db +} + func (s *DB) Save(value interface{}) *DB { return s.clone().do(value).begin().save().commit_or_rollback().db } diff --git a/model.go b/model.go index b5cd3d21..65d8d622 100644 --- a/model.go +++ b/model.go @@ -134,6 +134,10 @@ func (m *Model) updatedColumnsAndValues(values map[string]interface{}, ignore_pr } data := m.reflectData() + if !data.CanAddr() { + return + } + for key, value := range values { if field := data.FieldByName(snakeToUpperCamel(key)); field.IsValid() { if field.Interface() != value {