サーバーサイドエンジニアの水戸です!
今回は、Goのクエリビルダー「goqu」についてご紹介します。
goquとは
goqu is an expressive SQL builder and executor
goquは表現力豊かなSQLビルダーおよびエグゼキューターです
goquとは、GoにおけるSQLビルダーパッケージです。
SQLを実行することもできますが、ORマッパーのように扱うことは推奨されておらず、その場合はgormやhoodの利用が推奨されています。
goquでクエリを構築し、 database/sql
などを用いてSQLを実行するという使い方をすることになりそうです。
使ってみる
SQLの発行
package main import ( "fmt" "github.com/doug-martin/goqu/v9" ) func main() { sql, _, err := goqu.From("hoge").ToSQL() fmt.Println(sql) // Output: // SELECT * FROM "hoge" }
このように、簡単にSQLを発行することができます。 カラムや条件の指定は以下のように行います。
sql, _, err := goqu. From("hoge"). Select("a", "b", "c"). Where(goqu.Ex{"id": 1}). ToSQL() fmt.Println(sql) // Output: // SELECT "a", "b", "c" FROM "hoge" Where "id" = 1
goqu.Ex
は、key-valueの形になるマップです。
基本的にはWhere句で使われ、カンマ区切りで複数指定することも可能です。
Prepared Statementを利用する
Prepared Statementを利用するには、ToSQLする前に Prepared(true)
と記述します。
dataSet := goqu. From("hoge"). Where(goqu.Ex{"id": 1, "name": "foo"}) sql, args, _ := dataSet.ToSQL() fmt.Println(sql, args) // Output: // SELECT * FROM "hoge" WHERE (("id" = 1) AND ("name" = 'foo')) [] sql, args, _ = dataSet.Prepared(true).ToSQL() fmt.Println(sql, args) // Output: // SELECT * FROM "hoge" WHERE (("id" = ?) AND ("name" = ?)) [1 foo]
”方言”をいい感じに切り替える
RDBMSによって若干記法が異なるSQLが存在します。
本来ならば、使っているRDBMSによって記法を変える必要がありますが、goqu.Dialectを使うことでいい感じに切り替えてくれます。
// 使う言語のdialectをimportする必要がある // importがない状態でDialectを指定してもエラーにならないので注意 import ( _ "github.com/doug-martin/goqu/v9/dialect/postgres" _ "github.com/doug-martin/goqu/v9/dialect/mysql" ) sql, _, err := goqu. Dialect("mysql"). Select("a", "b", "c"). From("hoge"). ToSQL(); fmt.Println(sql); // -> SELECT `a`, `b`, `c` FROM `hoge`; sql, _, err := goqu. Dialect("postgres"). Select("a", "b", "c"). From("hoge"). ToSQL(); fmt.Println(sql); // -> SELECT "a", "b", "c" FROM "hoge";
発行されるクエリが変わっていることが確認できます。
この機能を使えば、複数のRDBMSを使っているシステムでもgoquひとつで完結です。
応用編〜INSERT ON DUPLICATE KEY UPDATE〜
応用編として、業務で利用したコードをご紹介します。
(テーブル名やスキーマは仮です)
READMEのドキュメントには記載されていませんが、パッケージのリファレンスに記載されている方法で実現することができます。
ちなみに、READMEにリファレンスへのリンクないのかな?と思っていたら、小さくリンク貼ってありました。(気づかなかった)
mysql := goqu.Dialect("mysql") postgres := goqu.Dialect("postgres") sql, args, _ := mysql. Insert("hoge"). Cols("a", "b"). Vals(goqu.Vals{"hoge", "huga"}). OnConflict(goqu.DoUpdate("a", goqu.Record{ "c": goqu.L("c+1"), })). Prepared(true). ToSQL() fmt.Println(sql, args) // Output: // INSERT IGNORE INTO `hoge` (`a`, `b`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `c`=c+1 [hoge huga] sql, args, _ := postgres. Insert("hoge"). Cols("a", "b"). Vals(goqu.Vals{"hoge", "huga"}). OnConflict(goqu.DoUpdate("a", goqu.Record{ "c": goqu.L("c+1"), })). Prepared(true). ToSQL() fmt.Println(sql, args) // Output: // INSERT INTO "hoge" ("a", "b") VALUES ($1, $2) ON CONFLICT (a) DO UPDATE SET "c"=c+1 [hoge huga]
OnConflictとDoUpdateを組み合わせることで実現できます。
ただし、見てお分かりの通り、mysqlで指定したSQLに IGNORE
という余分な句が含まれているため、mysqlで使う場合は文字列操作を行って削除する必要があります。
(これが意図してこうなっているのか、はたまたバグなのかは不明です)
最後に
今回はGoのクエリビルダー「goqu」の使い方の一例をご紹介しました。
今回紹介した他にも、InsertやUpdateのときにstructのsliceを引数に渡すだけで簡単にSQLが発行できたり、もっと複雑なSQLの発行もできます。
詳しくは、リポジトリのREADMEやgoquのリファレンスをご覧いただき、実際に使ってみてください。