gormのUpdateColumnsでモデル内のboolゼロ値(false)を持ったカラムが更新されなかった
schema
mysql> desc events; +-------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+-------+ | id | int(10) | YES | | NULL | | | name | varchar(255) | YES | | NULL | | | flag | tinyint(1) | YES | | NULL | | +-------+--------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
問題
type Event struct { Id uint `json:"id"` Name string `json:"name"` Flag bool `json:"flag"` } func gormConnect() *gorm.DB { db, err := gorm.Open("mysql", "root@tcp(localhost:3306)/sample") if err != nil { panic(err.Error()) } return db } func hoge(c echo.Context) { db := gormConnect() // 初期データ作成 eventEx := Event{Id: 1, Name: "taro", Flag: true} result := db.Create(eventEx) fmt.Println(result) // 初期データのID=1を更新する eventExBefore := Event{Id: 1} db.First(&eventExBefore) eventExAfter := new(Event) // {Name: "jiro", Flag: false} という値がPUTされてきたとする c.Bind(eventExAfter) db.Model(&eventExBefore).UpdateColumns(&eventExAfter) }
Createのタイミングで以下のINSERT文が発行される
INSERT INTO `events` (`id`,`name`,`flag`) VALUES (1,'taro',1);
Updateで以下のSQL発行を期待するが、、、
UPDATE `events` SET `name` = 'jiro', `flag` = 0 WHERE `events`.`id` = 1;
実際には以下のSQLが発行される
UPDATE `events` SET `name` = 'jiro' WHERE `events`.`id` = 1;
以下のとおり、bool型ゼロ値ではないtrueを指定すると
func main() { db := gormConnect() // 初期データ作成 eventEx := Event{Id: 1, Name: "taro", Flag: true} result := db.Create(eventEx) fmt.Println(result) // 初期データのID=1を更新する eventExBefore := Event{Id: 1} db.First(&eventExBefore) eventExAfter := new(Event) // {Name: "jiro", Flag: true} という値がPUTされてきたとする c.Bind(eventExAfter) db.Model(&eventExBefore).UpdateColumns(&eventExAfter) }
期待どおり動作する
INSERT INTO `events` (`id`,`name`,`flag`) VALUES (1,'taro',0); UPDATE `events` SET `name` = 'jiro', `flag` = 1 WHERE `events`.`id` = 1;
なんでこうなるのか
func (s *DB) UpdateColumns(values interface{}) *DB {
ここで呼ばれたタイミングではvaluesに値が渡ってきてるけど、その後instanceにセットしてcallback呼んだタイミングで消えてるので、
Setするどっかのロジックでゼロ値だとスキップするような動きがあると思うけどどこかわからんかった・・・
追記 以下でした
以下のように map[string]interface{}{}
に更新パラメータをバインドさせてUpdateすれば意図した動きになるが、Structに定義されたカラム全てが載ってしまい、思った使い方ができなかった。
event := map[string]interface{}{} if err := c.Bind(¶ms); err != nil { return err }
期待どおりに動かすには
boolポインタ型を使用する。
--- a/gorm-sample.go +++ b/gorm-sample.go @@ -10,7 +10,7 @@ import ( type Event struct { Id uint `json:"id"` Name string `json:"name"` - Flag bool `json:"flag"` + Flag *bool `json:"flag"` } func gormConnect() *gorm.DB { @@ -24,15 +24,17 @@ func gormConnect() *gorm.DB { func main() { db := gormConnect() + ptrue := &[]bool{true}[0] + pfalse := &[]bool{false}[0] // 初期データ作成 - eventEx := Event{Id: 1, Name: "taro", Flag: false} + eventEx := Event{Id: 1, Name: "taro", Flag: ptrue} result := db.Create(eventEx) fmt.Println(result) // 初期データのID=1を更新する eventExBefore := Event{Id: 1} db.First(&eventExBefore) eventExAfter := new(Event) // {Name: "jiro", Flag: false} という値がPUTされてきたとする c.Bind(eventExAfter) db.Model(&eventExBefore).UpdateColumns(&eventExAfter) }
こうするとboolポインタ型のゼロ値はnilとなり、falseと値を指定した場合も意図した動きになる。
eventExAfter := map[string]interface{}{} // {Name: "jiro", Flag: false} という値がPUTされてきたとする c.Bind(eventExAfter) db.Model(&eventExBefore).UpdateColumns(&eventExAfter)
ちなみに、この挙動は github.com/go-playground/validator
を使用した際にも同じだった。
flag: falseと指定していた場合もrequire指定でエラーになってしまう。
uintなどを使用した場合にもゼロ値が 0
であるため、0で更新したい場合などは↑のようにuintポインタ型にしないとだめそう。
他にうまい回避方法がわからなくて、こんなことしたけど実はイケてるやり方があったら知りたい。。。