Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

Goのクエリビルダー「goqu」を使ってみた ~業務で使った際の応用も紹介~

サーバーサイドエンジニアの水戸です!

今回は、Goのクエリビルダー「goqu」についてご紹介します。

goquとは

goqu is an expressive SQL builder and executor

goquは表現力豊かなSQLビルダーおよびエグゼキューターです

goquとは、GoにおけるSQLビルダーパッケージです。

SQLを実行することもできますが、ORマッパーのように扱うことは推奨されておらず、その場合はgormhoodの利用が推奨されています。

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のリファレンスをご覧いただき、実際に使ってみてください。