最近、仕事でGoを触ることが多いです。
GORMを使っているプロジェクトで面白いバグを発見したのでシェアします。
ミスる人が多そうなバグだなとも思いました。
はじめに
GORMでDBとマッピングするための構造体を書く際、下記のようになると思いますが、これは明らかに間違っています。
どこでしょうか?
type Person struct {
Name string `json:"name"`
Age uint `json:"age"`
DeletedAt time.Time `json:"deleted_at"`
}
正解は、DeletedAt
がtime.Time
型になっていることです。
なぜこの実装がまずいのか説明します。
解説
先に正解を示すと下記です。
type Person struct {
Name string `json:"name"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
DeletedAt
は、Nullableな属性です。(論理削除されたタイミングで初めて値が入る)。
ですが、time.Time
型を使っていると、テーブルに保存されるタイミングで 0000-00-00 00:00
という値が入ってしまい、Nullでなくなってしまいます。(重要)
「削除されていない列を取得したい」というケースでは、往々にして
select * from <table name> where deleted_at is null;
のようなSQLが実行されますが、上記の問題が発生するとこのSQLは機能しません。
そこで、gorm.DeletedAt
を使うことで保存時に上記の問題が発生することを防ぐことができ、Nullが入ったまま保存が可能となり、問題は解消します。
ちなみに、gorm.DeletedAt
は sql.NullTime
のDefined type(日本語でどう表現するのかわからない)です。
どっちを使ってもいいですが、個人的には前者の方が分かりやすいと思います。
https://github.com/go-gorm/gorm/blob/810058cd55e8a92f031b5ce3c0e5b7918911b3f3/soft_delete.go#L13
created_at
や updated_at
はNULLになることがないため、time.Time
でも大丈夫だと思いますが、
deleted_at
も同じノリでやっちゃうと罠にはまるねというお話しでした。