最近携わっているとある案件では、CRUD操作はsqlcで生成したORMを使い、QueryServiceではgoquを使ってクエリを組み立てています。
以前の記事ではsqlcについて紹介しましたので、今回はgoquについて紹介したいと思います。
まず初めに、goquについて簡単に紹介します。
goquは複数のDBに対応した多機能のクエリビルダーです。
goquで提供されている様々なExpressionsなどを利用して、複雑なクエリを楽に楽しく記述することができます。
また、クエリを組み立てるだけでなく、直接クエリを実行することもできます。
更に、複数のレコードをスキャンして、構造体やプリミティブにマッピングすることもできます。ただし、goquはORMとして使用されることを想定してるわけではないので、アソシエーションやフックといった機能は提供していません。
投稿時点では、goquは以下のDBのクエリ生成に対応しています。
また、これら以外の書式であっても、Custom Dialectsを定義することで対応することができます。
各クエリ操作を行う際にはまず、特定のDBの書式にあったクエリを出力するために、Dialect関数を使用してDiralectWrapperを生成します。
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に対応するパッケージをインポートしておく必要があります。
_ "github.com/doug-martin/goqu/v9/dialect/mysql"
ちなみに、Dialectを使わないでクエリを生成する方法もありますが、便宜上、本記事では全てDialectを使ってクエリを生成します。
Insert句を生成するには、生成したDialectのInsert()
メソッドにCols()
メソッドとVals()
メソッドをチェインする形で行います。
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()
メソッドをチェインする書き方もできます。
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: []
すでに構造体が定義されている場合は、それを使うこともできます。
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()
メソッドと必要に応じてWhere()
メソッドなどをチェインして行います。
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などの関数も使えます。
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: []
すでに構造体が定義されている場合は、それを使うこともできます。
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()
メソッドとSet()
メソッドなどをチェインして行います。
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: [] }
すでに構造体が定義されている場合は、それを使うこともできます。
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()
メソッドをチェインして行います。
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は、Where、From、Selectなどの部分のより詳細にな記述をサポートします。すべては紹介しきれないので、よく使われそうなものをピックアップして紹介します。
Ex{}
Ex{}
はWhere句で最も使われるであろう型で、columnとvalueを等号演算子で比較する際にまとめて記述するために使われます。
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で判定されます。
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()
などと一緒に使われます。
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()
はクエリで直接値を使いたい場合に使われます。
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()
の中で複数の条件を定義する際に使われます。
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()
の中で使われます。
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: []
デフォルトでは、goquはすべてのパラメータを直接クエリに埋め込みますが、パラメータを分けたい場合は、Prepared()
を使うことでこれを実現します。
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]
ScanStructs()
を使うことで、Select句で指定したcolumnを構造体に反映することができます。
注意点としては、事前にsql.Open()
でコネクションを張っておき、それをDiralectWrapperのDB()
メソッドで渡しておく必要があります。
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良さそうだなと思っていただけた方は、ぜひドキュメントをご覧ください。