Overview
ライブラリの紹介記事です。
squirrel という Go の クエリビルダーが便利だったのでその紹介です。
何をしてくれるのか
ORM 的にメソッドチェーンでクエリを動的に組み立ててくれます。
実際に使うときは README を参照してクエリを組み立ててください。最後に ToSql()
を呼ぶと SQL と Placeholder に指定した値の slice (interface{} 型だけど)が出力されます。
以下はサンプルです。
Sample1
import sq "github.com/Masterminds/squirrel" query, args, err := sq.Select("*").From("users").Where(sq.Eq{"id": 1}).ToSql() // select * from user where id = 1;
Sample2
import sq "github.com/Masterminds/squirrel" query, args, err := sq.Select("*").From("users").Where( sq.And{ sq.Eq{"gender": "male"}, sq.Eq{"age": 10}, }, ).ToSql() // select * from user where gender = "male" and age = 10;
クエリを組み立てる "だけ” の機能を提供してくれるので以下のような効用があると思います。
*1. クエリを文字列で組み立てるケースで十分だとも思いますが、スペースの取り扱いをミスってクエリエラーが発生したり、クエリのフォーマットが個々人で書き方バラバラになったりといったストレスはこういったクエリビルダーを使った方が低減されると思います。
何をしてくれないのか
クエリを組み立てるだけなので、実際に DB と接続して SQL を叩くのは別のライブラリを使う必要があります。
※ ただ、それがこのライブラリの良さだと考えています。
ハマったこと
In が明示的に用意されていない
SQL の In 句がクエリビルダーに用意されてないのかと思って以下の issue を参考に In のヘルパー関数を用意しようかと思いましが、
以下のような書き方で In 句が出力されることを教えてもらいました。
query, args, err := sq.Select("*").From("users").Where(sq.Eq{"id": ids}).ToSql()
まとめ
依存が少ない状態で、ユースケースに応じて動的なクエリを変更したいケースでは愚直に文字列を繋いでくことを是として考えましたが、こう言ってクエリビルダーのみの機能を提供してくれるライブラリを知りました。他にもあると思いますが、 squirrel は提供してくれている機能もシンプルでいいライブラリだなと思います。
余談
カラムを全て取得する
squirrel と関係ないですが、SQL を発行する際にすべてのカラムを select クエリに書く(= ワイルドカードをつかない)のがめんどくさいので、struct のフィールドに指定したマッピング用の付加情報タグから、カラム情報を取得する実装をメモ程度に残しておきます。
type User struct { ID int64 `$TagName:"id"` Name string `$TagName:"name"` } func main() { user := User{} t := reflect.TypeOf(user) for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("%v\n", field.Tag.Get("$TagName")) } }
https://go.dev/play/p/8gjhDrGQrD7 は sqlx でマッピング用のフィールドには db
タグを使うので db
タグの付いたカラム名一覧を取り出す実装です。毎回 reflect が必要なのがちょっとネックなポイントだなぁとは思っています。
追記
ちなみにワイルドカードをカラム指定とreflect によるカラム取得をベンチしてみたところワイルドカードが単純なパフォーマンスは一番いいことがわかりました。これはおそらく colums に渡す slice の大きさに比例していそうです。
$ go test --bench . --benchmem goos: darwin goarch: amd64 pkg: sample.com/go-sandbox cpu: VirtualApple @ 2.50GHz Benchmark_WildCard-10 484780 2097 ns/op 1688 B/op 33 allocs/op Benchmark_PlainColumns-10 386214 2869 ns/op 2240 B/op 45 allocs/op Benchmark_Columns-10 316792 3630 ns/op 2776 B/op 56 allocs/op
コードは以下
package main import ( "reflect" "testing" "time" sq "github.com/Masterminds/squirrel" ) type User struct { ID int64 `db:"id"` Name string `db:"name"` Age int64 `db:"age"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } func (u User) Columns() []string { var columns []string user := User{} t := reflect.TypeOf(user) for i := 0; i < t.NumField(); i++ { field := t.Field(i) columns = append(columns, field.Tag.Get("db")) } return columns } func Benchmark_WildCard(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { sq.Select("*"). From("`user`").ToSql() } } func Benchmark_PlainColumns(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { sq.Select(`id`, `name`, `age`, `created_at`, `updated_at`). From("`user`").ToSql() } } func Benchmark_Columns(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { sq.Select(User{}.Columns()...). From("`user_concierge_counseling`").ToSql() } }
余談ですが、暗黙的なカラムと言う話もあるのでワイルドカードをそもそも使うのが正しいのか、と言うのはユースケースに依存すると思います。