🥝

[Gorm v1] Updateまわりを観察する

ふだん使っているけど、なんとなく書いていたから、ここらでちゃんと観察してみようと思う

type User struct {
	ID      uint64
	Name    string
	Enabled bool
}

Save()

すべてのカラムを更新する

m.ID == 0 であれば INSERT、m.ID != 0 であれば UPDATE になる

var m User
db.Take(&m)

m.Enabled = false

db.Save(m)
// m.ID == 0 の場合
// INSERT INTO `user` (`name`,`email`) VALUES ('example','example@example.com')
// m.ID != 0 の場合
// UPDATE `user` SET `name` = 'example', `enebled` = false WHERE `user`.`id` = 1

m.ID != 0 のとき、なにも値を更新せずに Save するとエラーになる。 更新したけど、値が変わらなかった場合も同様。つまり、値が変わったときだけ UPDATE 文が実行される

var m User
db.Take(&m)

db.Save(m)
// panic: reflect.Value.Addr of unaddressable value

Update()

var m User
db.Take(&m)

m.Name = "name"
m.Enabled = true

db.Update(m)
// missing WHERE clause while updating
// Update() はあくまでカラム名を指定するためのもの

db.Model(&m).Update(m)
// Model を指定する必要がある
//
// m.ID == 0 の場合
// missing WHERE clause while updating
// m.ID != 0 の場合( Update() はあくまでカラム名を指定するものなので、id も入ってしまう)
// UPDATE `user` SET `enabled` = true, `id  = 1, `name` = 'name' WHERE `user`.`id` = 1

db.Model(&User{}).Where(m.ID).Update(m)
// Model() でレコードを特定できない場合は Where() を使う必要がある
// UPDATE `user` SET `enabled` = true, `id  = 1, `name` = 'name' WHERE `user`.`id` = 1 AND ((`user`.`id` = 1))

本来の使い方はこう

var m User
db.Take(&m)

db.Model(&m).Update("enabled", true)
// UPDATE `user` SET `enabled` = true  WHERE `user`.`id` = 1

db.Model(&m).Update(User{Name: "name", Enabled: true})
// UPDATE `user` SET `enabled` = true, `name` = 'name' WHERE `user`.`id` = 1

db.Model(&m).Update(map[string]{"name": "name", "enabled": true})
// UPDATE `user` SET `enabled` = true, `name` = 'name' WHERE `user`.`id` = 1

ゼロ値の扱いには注意が必要。構造体でカラムを指定する場合は、ゼロ値のフィールドがスキップされてしまう

var m User
db.Take(&m)

db.Model(&m).Update("enabled", false)
// UPDATE `user` SET `enabled` = false  WHERE `user`.`id` = 1

db.Model(&m).Update(User{Name: "", Enabled: true})
// Name がゼロ値の場合、更新されない
// UPDATE `user` SET `enabled` = true WHERE `user`.`id` = 1

db.Model(&m).Update(User{Name: "name", Enabled: false})
// Enabled がゼロ値の場合、更新されない
// UPDATE `user` SET `name` = 'name' WHERE `user`.`id` = 1

db.Model(&m).Update(User{Name: "", Enabled: false})
// ゼロ値のフィールドしかない場合、クエリ自体がスキップされる

db.Model(&m).Update(map[string]{"name": "name", "enabled": true})
// UPDATE `user` SET `enabled` = true, `name` = 'name' WHERE `user`.`id` = 1

db.Model(&m).Update(map[string]interface{}{"name": "", "enabled": false})
// map ならゼロ値で更新できる
// UPDATE `user` SET `enabled` = false, `name` = '' WHERE `user`.`id` = 1

Select() でカラムを指定すれば、構造体でもゼロ値で更新できる

var m User
db.Take(&m)

db.Model(&m).Select("name", "enabled").Update(User{Name: "", Enabled: false})
db.Model(&m).Select("*").Update(User{Name: "", Enabled: false})

v1 だと Update()Update() があって、Update() が内部的に Update() を呼んでいたが、v2 から明確に用途が分けられた