sqlcとは?
sqlcは、Go のORMですが、少し変わっていて、自分でSQLを書きます。「SQLを書きたくない!」という人には、絶望的に向いてないですが、「ORMが作るSQLは気に食わない」とか、「結局どんなSQL生成されてるのか気になるんだけど」とか何度か思ったことのある人(「ORM名 SQLをログに出す」とかでググったことのある人)には割と気に入るかもしれません。
sqlcを選んだ理由
今まで使ったORMは、順番でいくと、sqlx(は、ORMではないか)、BeegoのORM、sqlboilerです。gorm はコードレビューでは見たことがありますが、書いたことはないです。
正直、プロジェクトを変えるたびに変えているので、どうこう述べるほど使い込んではいないのですよね。
- sqlx ... SQL書くのは同じっちゃおなじだけど、ちょっと
- BeegoのORM ... 今回、Beego使わないから
- sqlboiler ... 割とよかった印象
- gorm ... メジャーだけど使ったことないな
ということで、sqlboiler か gorm かというところで、それ以外の選択肢を調べて、sqlcってのがあって面白そう、という感じで選びました。あえて sqlc を選んだ理由としては、単純に「なんだかんだ、SQL書いてるほうがわかりやすい」というところかもしれません。要するに、前段ですでに述べた理由が全てですかね。
デメリットとして、動的な検索条件(WHERE)は作りにくいというか作れないということはありますが、自動生成されたコードはかなり素直なので、それを参考に自分で新たなメソッドを定義すればなんとでもなります(別で書いてます)。
なお、利用しているデータベースは MySQL です。sqlc自体は当初PostgreSQL用で作られたとのことなので、PostgreSQLではできるけど、MySQLではできないということがいくつかあるようです。
SQLの書き方
単純プレースホルダ
-- name: GetUser :one
SELECT * FROM user where id = ?;
単純なケースはこんな感じですね。PostgreSQLでは ?の代わりに$1 を使えるようですが、MySQLでは使えないようです。
プレースホルダに名前をつける
以下のように、sqlc.arg(user_id)のようにすると、自動生成されるコードの変数名がuserIDとなり、ちょっとわかりやすくなります。
-- name: GetUserWithID :one
SELECT * FROM user where id = sqlc.arg(userID);
IN を使う
(?) とか書いてもだめでして、sqlc.slice(名前)のように書きます。
-- name: GetUsersWithIDs :many
SELECT * FROM user where id in sqlc.slice(userIDs);
INSERT/UPDATE
SQLの上のコメントの最後に、:oneや :many というのがついていますが、アノテーションといいます。INSERTやUPDATEでは、:execや:execresultを使います。:execresultを使うと、sql.Resultが返ってきます。
-- name: UpdateUsreNameWithID :exec
UPDATE user SET NAME = ? WHERE id = ?;
SUM()がintefaceで返ってくる
単純に下記のように書くと、sum(age)はinterfaceとして返ってきます。
-- name: GetUserSumAge :one
SELECT sum(age) FROM user;
以下のようにSQLの中でCASTする必要があります。
-- name: GetUserSumAge :one
SELECT CAST(SUM(age) AS unsigned) sum_age FROM user;
こうすると、uint64 で返ってきます。
JOINする
-- name: GetUserWithItem :many
SELECT * FROM user JOIN user_item as ui ON user.id = ui.user_id;
このように書いたとすると、user テーブルと user_item テーブルが合成された、GetUserWithItemRowというSQLに対応して作られた型で返ってきます。イメージこんな感じです。
type GetUserWithItemRow struct {
ID int
Name string
// ...
ID_2 int
UserID int
ItemID int
// ...
}
idのような重複するカラム名は、上記のように_2がついてしまいます。
ASを使う
もちろん、カラムを書き出してASで書けば、その名前にはなります。
-- name: GetUserWithItem :many
SELECT id, name, ui.id AS user_item_id FROM user JOIN user_item as ui ON user.id = ui.user_id;
とすれば、下記のような型になります。
type GetUserWithItemRow struct {
ID int
Name string
UserItemID int
}
sqlc.embedを使う
また、*で書いても良いなら、sqlc.embed マクロを使うのが便利です。
SELECT sqlc.embed(user), sqlc.embed(user_item) FROM user JOIN user_item as ui ON user.id = ui.user_id;
このようにすると、次のような typeで返ってきます。
type GetUserItemRow struct {
User User
UserItem UserItem
}
なお、sqlc.embed() には、自動生成されたテーブルごとの型を渡す必要があり、独自の型を渡すことは出来ません。
また、FROMやJOINの際にテーブル名にASを使うと、エラーになるようです。sqlc.embed使わない場合は、ASを使っても問題なかったです。
FROMやJOINの際にテーブル名にASを使った場合は、sqlc.embed に渡す名前もASで指定した名前を使ってください。そうすれば、問題なく使えます。
注意
SQLの最後に;を忘れないでください。;が終わりまでが一つのSQLですので、忘れると、全体が一つのSQLとみなされちゃいます。
アノテーションとマクロの解説
-
:one、:many、:exec、:execresult以外のアノテーションは、以下に解説があります -
sqlc.argのようなマクロは、以下に解説があります
終わり
というわけで、sqlcの紹介でした。
今の所、なかなかいい感じではないかと思います。
動的な条件の作り方、Typeのoverrideについても、別で書いています。