はじめに
最近携わっているとある案件では、CRUD操作はsqlcで生成したORMを使い、QueryServiceではgoquを使ってクエリを組み立てています。
以前の記事ではsqlcについて紹介しましたので、今回はgoquについて紹介したいと思います。
goquとは
まず初めに、goquについて簡単に紹介します。
goquは複数のDBに対応した多機能のクエリビルダーです。
goquで提供されている様々なExpressionsなどを利用して、複雑なクエリを楽に楽しく記述することができます。
また、クエリを組み立てるだけでなく、直接クエリを実行することもできます。
更に、複数のレコードをスキャンして、構造体やプリミティブにマッピングすることもできます。ただし、goquはORMとして使用されることを想定してるわけではないので、アソシエーションやフックといった機能は提供していません。
対応するDB
投稿時点では、goquは以下のDBのクエリ生成に対応しています。
- MySQL
- PostgreSQL
- SQLite3
- SQL Server
また、これら以外の書式であっても、Custom Dialectsを定義することで対応することができます。
基本的な使い方
各クエリ操作を行う際にはまず、特定のDBの書式にあったクエリを出力するために、Dialect関数を使用してDiralectWrapperを生成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package main import ( "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") ds := dialect.From("users") sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users`, Args: [] } |
注意点としては、使用したいDBに対応するパッケージをインポートしておく必要があります。
1 | _ "github.com/doug-martin/goqu/v9/dialect/mysql" |
ちなみに、Dialectを使わないでクエリを生成する方法もありますが、便宜上、本記事では全てDialectを使ってクエリを生成します。
Insert句を生成する
Insert句を生成するには、生成したDialectの
Insert()
メソッドに
Cols()
メソッドと
Vals()
メソッドをチェインする形で行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") ds := dialect.From("users").Insert(). Cols("first_name", "last_name"). Vals(goqu.Vals{"John", "Doe"}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: INSERT INTO `users` (`first_name`, `last_name`) VALUES ('John', 'Doe'), Args: [] } |
または、
Rows()
メソッドをチェインする書き方もできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | ds := dialect.From("users").Insert().Rows( goqu.Record{ "first_name": "John", "last_name": "Doe", }, ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: INSERT INTO `users` (`first_name`, `last_name`) VALUES ('John', 'Doe'), Args: [] |
すでに構造体が定義されている場合は、それを使うこともできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | type User struct { FirstName string LastName string `db:"last_name"` // タグを使ってカラム名を指定 } ds := dialect.From("users").Insert(). Rows(User{FirstName: "John", LastName: "Doe"}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: INSERT INTO `users` (`firstname`, `last_name`) VALUES ('John', 'Doe'), Args: [] |
Select句を生成する
Select句を生成するには、
Select()
メソッドと必要に応じて
Where()
メソッドなどをチェインして行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") ds := dialect.From("users"). Select("first_name"). Where(goqu.Ex{"id": 1}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT `first_name` FROM `users` WHERE (`id` = 1), Args: [] } |
COUNTやMAXなどの関数も使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | ds := dialect.From("users"). Select( goqu.COUNT("id").As("count"), goqu.MAX("age").As("max_age"), goqu.AVG("age").As("avg_age"), ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT COUNT(`id`) AS `count`, MAX(`age`) AS `max_age`, AVG(`age`) AS `avg_age` FROM `users`, Args: [] |
すでに構造体が定義されている場合は、それを使うこともできます。
1 2 3 4 5 6 7 8 9 10 11 12 | type User struct { FirstName string `db:"first_name"` LastName string `db:"last_name"` } ds := dialect.From("users").Select(&User{}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT `first_name`, `last_name` FROM `users`, Args: [] |
Update句を生成する
Update句を生成するには、
Update()
メソッドと
Set()
メソッドなどをチェインして行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package main import ( "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") ds := dialect.Update("users"). Set(goqu.Record{ "first_name": "John", "last_name": "Doe", }). Where(goqu.Ex{"id": 1}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: UPDATE `users` SET `first_name`='John',`last_name`='Doe' WHERE (`id` = 1), Args: [] } |
すでに構造体が定義されている場合は、それを使うこともできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | type user struct { FirstName string `db:"first_name"` LastName string `db:"last_name"` } ds := dialect.Update("users"). Set(user{FirstName: "John", LastName: "Doe"}). Where(goqu.Ex{"id": 1}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: UPDATE `users` SET `first_name`='John',`last_name`='Doe' WHERE (`id` = 1), Args: [] |
Delete句を生成する
Delete句を生成するには、
Delete()
メソッドをチェインして行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package main import ( "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") ds := dialect.Delete("users").Where(goqu.Ex{"id": 1}) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: DELETE `users` FROM `users` WHERE (`id` = 1), Args: [] } |
より実践的な使い方
goquでの基本的なCRUD操作の紹介が終わったところで、次はより実践的な機能を紹介します。
Expressionsを使ってクエリを組み立てる
Expressionsは、Where、From、Selectなどの部分のより詳細にな記述をサポートします。すべては紹介しきれないので、よく使われそうなものをピックアップして紹介します。
Ex{}
Ex{}
はWhere句で最も使われるであろう型で、columnとvalueを等号演算子で比較する際にまとめて記述するために使われます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ds := dialect.From("users"). Where( goqu.Ex{ "col1": 1, "col2": "val2", "col3": true, "col4": nil, "col5": []string{"val1", "val2"}, }, ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE ((`col1` = 1) AND (`col2` = 'val2') AND (`col3` IS TRUE) AND (`col4` IS NULL) AND (`col5` IN ('val1', 'val2'))), Args: [] |
ExOr{}
ExOr{}
は
Ex{}
と似ていますが、各条件がAndではなくOrで判定されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ds := dialect.From("users"). Where( goqu.ExOr{ "col1": 1, "col2": "val2", }, ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE ((`col1` = 1) OR (`col2` = 'val2')), Args: [] |
C()
C()
はcolumnを表す識別子で、
Where()
などと一緒に使われます。
1 2 3 4 5 6 7 8 9 | ds := dialect.From("users"). Where(goqu.C("col1").Eq(1)) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE (`col1` = 1), Args: [] |
V()
V()
はクエリで直接値を使いたい場合に使われます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | ds := dialect.From("users"). Select( goqu.V(true).As("is_valid"), goqu.V(0.5).As("tax_rate"), ). Where(goqu.C("col1").Eq(1)) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT 1 AS `is_valid`, 0.5 AS `tax_rate` FROM `users` WHERE (`col1` = 1), Args: [] |
And()
And()
は
Where()
の中で複数の条件を定義する際に使われます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ds := dialect.From("users"). Where( goqu.And( goqu.C("col1").Eq(1), goqu.C("col2").Eq(2), ), ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE ((`col1` = 1) AND (`col2` = 2)), Args: [] |
Or()
Or()
も
And()
と同様に
Where()
の中で使われます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ds := dialect.From("users"). Where( goqu.Or( goqu.C("col1").Eq(1), goqu.C("col2").Eq(2), ), ) sql, args, err := ds.ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE ((`col1` = 1) OR (`col2` = 2)), Args: [] |
Prepared Statementsを使う
デフォルトでは、goquはすべてのパラメータを直接クエリに埋め込みますが、パラメータを分けたい場合は、
Prepared()
を使うことでこれを実現します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ds := dialect.From("users"). Where( goqu.Ex{ "col1": 1, "col2": "b", "col3": true, "col4": []int{1, 2, 3}, }, ) sql, args, err := ds.Prepared(true).ToSQL() if err != nil { panic(err) } fmt.Printf("SQL: %s, Args: %v\n", sql, args) // SQL: SELECT * FROM `users` WHERE ((`col1` = ?) AND (`col2` = ?) AND (`col3` IS TRUE) AND (`col4` IN (?, ?, ?))), Args: [1 b 1 2 3] |
Scanした結果を構造体に反映する
ScanStructs()
を使うことで、Select句で指定したcolumnを構造体に反映することができます。
注意点としては、事前に
sql.Open()
でコネクションを張っておき、それをDiralectWrapperの
DB()
メソッドで渡しておく必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package main import ( "database/sql" "fmt" "github.com/doug-martin/goqu/v9" // Import the mysql dialect _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) func main() { dialect := goqu.Dialect("mysql") mysqlDB, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/test") if err != nil { panic(err) } dialect.DB(mysqlDB) type user struct { FirstName string `db:"first_name"` LastName string `db:"last_name"` Age int `db:"-"` // Ignored } var users []user if err := dialect.From("users"). Select(&user{}). ScanStructs(&users); err != nil { panic(err) } fmt.Printf("Users: %+v\n", users) // Users: [{FirstName:John LastName:Doe Age:0}] } |
さいごに
ここでは主にgoquの基本的な使い方を紹介しましたが、まだまだたくさんの便利な機能がありますので、goqu良さそうだなと思っていただけた方は、ぜひドキュメントをご覧ください。