sqlcで動的な検索条件を作りたい
前回に続きまして、GoのORM sqlcの話です。前回の紹介記事で書いたとおり、sqlcで動的な検索条件(WHERE)を自動生成で行うのは無理ですが、以下の手順でやれば、そんなに難しくはありません。
Step 1. 単純なSQLを書く
-- name: SearchUser :many
SELECT * FROM user;
Search
と書いてますが、特に検索はしてませんね。これは、下記のようなコードができあがります。
package your_db
// 省略
const searchUsers = `-- name: SearchUsers :many
select id, family_name, first_name, age, address from user`
func (q *Queries) SearchUsers(ctx context.Context) ([]User, error) {
rows, err := q.db.QueryContext(ctx, searchUsers)
if err != nil {
return nil, err
}
defer rows.Close()
var items []User
for rows.Next() {
var i User
if err := rows.Scan(
&i.ID,
&i.FamilyName,
&i.FirstNmae,
&i.Age,
&i.Address,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
めっちゃ単純ですね。SQLがconst searchUsers
に定義されていて、それを実行しているだけです。ちなみに、*
で書いた場合でも、上記のようにテーブルの持っているカラム名に展開してくれます。
このようなコードですので、これを参考に自分で検索条件を追加することは割と簡単ですね。
Step 2. 自動生成されたコードを参考に自分のメソッドを作る
コード生成されたところと同じ場所に、適当なファイル名で下記のような内容のファイルを作ります。
package your_db
import (
"context"
"strings"
)
const searchUsersWithCondition = `-- name: SearchUsers :many
select id, family_name, first_name, age, address from user WHERE`
type UserSearchCondition struct {
FamilyName *string
FirstName *string
Age *int
}
func (q *Queries) SearchUsersWithCondition(ctx context.Context, condition *UserSearchCondition) ([]User, error) {
conditions := make([]string, 0)
args := make([]any, 0)
if condition != nil {
if condition.FamilyName != nil {
conditions = append(conditions, "family_name = ?")
args = append(args, &condition.FamilyName)
}
if condition.FirstName != nil {
conditions = append(conditions, "first_name = ?")
args = append(args, &condition.FirstName)
}
if condition.Age != nil {
conditions = append(conditions, "age = ?")
args = append(args, &condition.Age)
}
}
rows, err := q.db.QueryContext(ctx, searchUsersWithCondition+strings.Join(conditions, " AND "), args...)
// 以下は同じなので省略
}
これで、UserSearchCondition
にデータを入れておけば、動的な検索条件を作れます。
自分で型定義して、検索条件を自前で作る必要がありますが、単純な条件であれば、割と簡単ですね。
ですが、「いちいち検索用のtypeを作るの?汎用性低くない?」という声が聞こえてきますね。
Step 3. Query Builderを使ってみる
そうですね、「ただのANDならこれでも良いが、複雑だとWHERE作るのがめんどい...」ですね。そういう時は、goquのような Query Builderを使うのが良いのではないかと思います。UserSearchCondition
のような自前の型を渡すのではなくて、goqu.Expression
を渡してやって、Query自体をgoquで作るというのもありかと思います。
func (q *Queries) SearchUsersWithCondition2(ctx context.Context, ex goqu.Expression) ([]User, error) {
sql, arg, err := goqu.Dialect("mysql").From("User").Where(ex).Prepared(true).ToSQL()
if err != nil {
return nil, err
}
// 以下省略
}
呼び出し側でgoquを使ってSQLの検索条件を作れます。変な条件ですが、例ということで。
ex = goqu.Or(
goqu.And(
goqu.C("first_name").Eq(firstName),
goqu.("age").Gt(10),
),
goqu.And(
goqu.C("first_name").Eq(firstName),
goqu.("age").Lt(20),
),
)
「カラム名やテーブル名のハードコーディングはちょっと...」という方や「typo するんですけど!!!」という僕のような方は、以下の記事で、information_schemaから型と定数作るやり方を紹介していますので、気が向いたらどうぞ。
goqu自体の紹介ブログも書いていますので、良ければ参考にしてください。
終わり
sqlcでは動的な検索条件は書けないのですが、自分で書くのはそんなに難しくないよっという話でした。
次回は、型のOverridesについて書こうと思います。