SQLBoilerで任意の型を設定する方法
はじめに
初めまして。 Belong で Junior Engineer として、主に Backend の開発を担当している、suzu です。 今回は Go 言語の ORM である、SQLBoiler にて任意の型を設定する方法についてまとめています。 詳細な設定方法に関する文献が多くなく、困った部分をまとめたので、参考になれば幸いです。
SQLBoiler とは
データベースのスキーマから ORM を生成できるライブラリです。 https://github.com/volatiletech/sqlboiler
他の ORM に比べた利点として、スキーマからのコード生成が行える為、 自身でスキーマとのマッピング等を考える必要なく、機能の実装に専念できるという点があります。
Belong での利用例
Belong では幾つかのプロダクトにおいて、 SQLBoiler を利用しての開発を行っています。 基本的な処理に関しては SQLBoiler の生成物を利用しつつも、サブクエリが必要な場合等の複雑なクエリに関しては、無理に SQLBoiler に頼らず、Raw Query を書く、という使い方をすることが多いです。 (その際のマッピング先として、 SQLBoiler にて生成された構造体を用いるケースもあります。)
型のオーバーライドについて
今回の主題である、型のオーバーライドについてです。 SQLBoiler では、データベースの型と Go の型の対応をドライバーが推論してくれます。 推論した型と違う型を使用したい場合、 Types1 設定をすることで、ドライバーが推論した型を任意の型でオーバーライドできます。
設定方法
sqlboiler.toml
に以下を記載することで、オーバーライドを設定できます。
- types.match
- 元々の要素、及び型の情報
- types.replace
- 対象の型
- types.imports
- replace で指定した型が定義されているパッケージ
例として MySQL データベースに定義した、テーブルtable_a
の要素date
を SQLBoiler を通じて、util.CustomDate
型で扱いたい場合を考えましょう。
その場合、以下のように記述できます。
[[types]]
[types.match]
type = "null.Time"
nullable = true
tables = ["table_a"]
name = "date"
[types.replace]
type = "util.CustomDate"
[types.imports]
third_party = ['"github.com/belong-inc/belong-lib/util"']
複数設定
設定したいオーバーライドが複数存在する場合は、以下のように並べて記述します。
[[types]]
[types.match]
type = "null.Time"
nullable = true
tables = ["table_a"]
name = "date"
[types.replace]
type = "util.CustomDate"
[types.imports]
third_party = ['"github.com/belong-inc/belong-lib/util"']
[[types]]
[types.match]
type = "null.Time"
nullable = true
tables = ["table_a"]
name = "price"
[types.replace]
type = "util.CustomFloat"
[types.imports]
third_party = ['"github.com/belong-inc/belong-lib/date"']
同名要素への一括設定
また、複数のテーブルの同名要素に対して、同じ設定をしたい場合は、まとめて設定できます。 (外部キーを持つカラムなどで役立てそうですね。)
[[types]]
[types.match]
type = "null.Time"
nullable = true
tables = ["table_a","table_b","table_c"]
name = "date"
[types.replace]
type = "util.CustomDate"
[types.imports]
third_party = ['"github.com/belong-inc/belong-lib/util"']
オーバーライドする型の実装
SQLBoiler で使用する型には、以下の Interface が実装されている必要があります。
driver.Valuer
driver.Value
に変換する際のもの
sql.Scanner
sql.Rows.Scan
での変換時のもの
例として、SQLBoiler で用いられている、 null.Time2 のコードを見てみましょう。
null.Time は、Valid
フィールドを持ち、Time
フィールドがnil
の場合はValid
をfalse
にするという形で nullable な値を表現しています。
// Time is a nullable time.Time. It supports SQL and JSON serialization.
type Time struct {
Time time.Time
Valid bool
}
Scan
Scan は DB からの値を受け取る際のメソッドです。
.(type)
で型判定を行い、time.Time
型の場合はそのまま代入し、nil
の場合はValid
をfalse
にして返します。
// Scan implements the Scanner interface.
func (t *Time) Scan(value interface{}) error {
var err error
switch x := value.(type) {
case time.Time:
t.Time = x
case nil:
t.Valid = false
return nil
default:
err = fmt.Errorf("null: cannot scan type %T into null.Time: %v", value, value)
}
t.Valid = err == nil
return err
}
Value
Value は DB へどのように値を渡すかを定義するメソッドです。
Valid
がfalse
の場合はnil
を返し、それ以外の場合はtime.Time
型の値を返します。
// Value implements the driver Valuer interface.
func (t Time) Value() (driver.Value, error) {
if !t.Valid {
return nil, nil
}
return t.Time, nil
}
まとめ
今回は、SQLBoiler で任意の型を設定する方法についてまとめました。
Belong ではエンジニアを募集しています。Go に 興味のある方、技術を用いたビジネス課題の解決に興味がある方など、以下リンクをぜひご覧ください!