環境
- SQLBoiler v4
はじめに
SQLBoiler の README 及び、そこからリンクが貼られているコンテンツを読めばここに書いていることは網羅されていますが、初見では少し分かりづらいところ、少し複雑なユースケースをまとめています。
SQLBoiler の面倒な点
特に、SQLBoiler では以下の処理が面倒(または少し分かりづらい)ですが、その点も本文中で解説しています。
- Bind を使いデータをカスタム struct にマッピングする際に、alias を付ける必要がある点。
- リレーションを辿る方法。
Bind 利用例
カスタムの struct にデータをマッピングする場合、Bind
を使う必要があります。
以下、README → Videos の内、SQLBoiler: Advanced Queries and Relationships の25分目付近から説明されている内容を、まとめたものです。
以下、複数テーブルから値を返したい場合の例(↑の youtube からの例)。
type joinStruct struct {
User models.User `boil:"users,bind"`
Video models.Video `boil:"videos,bind"`
}
var joins []*joinStruct
rows, err := db.QueryContext(context.Background(),
`select users.id as "users.id",
users.name as "users.name",
videos.id as "videos.id",
videos.name as "videos.name"
from users inner join videos on users.id = vodies.user_id`,
)
dieIf(err)
err = queries.Bind(rows, &joins)
fmt.Println(len(joins))
fmt.Println(joins[0].User.Name, joins[0].Video.Name)
- ここでは users 及び videos テーブルのカラムを select に含めているため、カスタム struct(ここでは joinStruct という名前)を使う必要があります。
- 例えば
InnerJoin
を使っていても、単一のテーブルからだけ select する場合は、カスタム struct を使う必要はないですし、Bind
を使う必要もありません。Bind
を使いたければ、使うことは出来ます
- 例えば
- テーブルのカラムを struct にマッピングするために、alias を付ける必要があります(e.g.
select users.id as "users.id"
)- これが無いと、意図しないマッピングがされます。
- select 文で指定するカラムの内、複数テーブルで同じカラム名を持つものだけを alias を付ける必要があります。
- 上記の例では、id と name が users と videos テーブルに含まれるため、alias を付けています。
- それ以外のカラムに alias を付けても害はありません。
- select で指定しなかったフィールドも、初期値(e.g. int であれば 0)が入って返ってくる点に注意。つまり、struct 内のフィールドへのアクセスは可能。
詳しくは README の Binding のセクション と Bind のドキュメント を参照。
Bind を使った query でエラーが出た場合
エラーが発生する条件を再現した raw query を実行すれば、詳細なエラーが確認できる(ことがあります)。
例えば、Go 上の error では以下の ERROR
部分しか表示されなくても、raw query を実行する事により、LINE..
部分のエラーも表示されます。
ERROR: column reference "id" is ambiguous
LINE 21: ...s.id WHERE ("users"."organization_id" = 1) ORDER BY id DESC LI...
Eager Loading
Load を使うことにより、eager loading が可能となります(多段も可)。
models.Users(qm.Load("Videos.Tags"))
or
qm.Rels
を使うと型安全にアクセス可能。
models.Users(
qm.Load(
qm.Rels(
models.UserRels.Videos,
models.VideoRels.Tags,
)
)
)
以下のデータ構造を想定。
- user 1-n videos
- videos n-n tags (through tags_videos table)
Eager Loading を使うことで、以下のように複数の SQL が実行されるようになる。
データ量が多いテーブルへアクセスする場合は、left outer join を使わず、複数の SQL を発行するべき。
SELECT * FROM "users" WHERE ("users"."id" IN ($1));
[1]
SELECT * FROM "videos" WHERE ("videos"."user_id" IN ($1)) AND ("videos"."parent_id" is null);
[1]
SELECT * FROM "tags" WHERE ("tags"."video_id" IN ($1,$2));
[71 72]
尚、Eager Loading
については、Rails の Eager Loading
とは挙動が違う(Preload と同じ)ようなので、違いは以下を参照。
- https://stackoverflow.com/questions/69242991/when-is-better-to-use-preload-or-eager-load-or-includes
- https://qiita.com/ryosuketter/items/097556841ec8e1b2940f
リレーションを辿る方法
上記の Load
を利用して取得した リレーション
は model 定義の struct の R
に保存されます(以下の例では R *userR
)。
そして、以下の例の通り user.R.GetDepartment()
や user.R.GetContacts()
という形でリレーションを辿ることが出来ます。
これらは全て sqlboiler が自動生成したコードです。
// sqlboiler/models/user.go (sqlboiler が自動生成したコード)
type User struct {
<snip>
R *userR `boil:"-" json:"-" toml:"-" yaml:"-"`
L userL `boil:"-" json:"-" toml:"-" yaml:"-"`
}
// userR is where relationships are stored.
type userR struct {
Department *Department `boil:"Department" json:"Department" toml:"Department" yaml:"Department"`
}
// NewStruct creates a new relationship struct
func (*userR) NewStruct() *userR {
return &userR{}
}
// has-one or belongs-to の例
func (r *userR) GetDepartment() *Department {
if r == nil {
return nil
}
return r.Department
}
// has-many の例
func (r *userR) GetContacts() UserContactsSlice {
if r == nil {
return nil
}
return r.UserContactsSlice
}
// 以下、同様に他のリレーション用の GetXXX() メソッドが定義されている。
詳しくは、README の Relationships セクション を参照。
リレーションシップを持った struct を作る方法
※つまり、struct の R
(sqlboiler が生成した struct で relationship が保存される field)配下にデータを生成する方法。
※基本的にテストコード記述時に利用。通常のロジックでは不要。
上記と同じ User struct を例に使います。
上記例で定義されている (*userR) NewStruct()
を使います。
user := &models.User{DepartmentID: int64(1)}
user.R = user.R.NewStruct() // ↑のコードにある `(*userR) NewStruct()` を使う
user.R.Department = &models.Department{ID: int64(1)}
fmt.Println(user.R.Department.ID) // R 経由で Department にアクセス可能となる。
raw query の確認方法
boil.DebugMode = true
その他
SQLBoiler 本体でサポートされていない BULK 処理等の拡張を提供しているレポジトリ:
https://github.com/tiendc/sqlboiler-extensions