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
を使っても問題なかったです。
注意
SQLの最後に;
を忘れないでください。;
が終わりまでが一つのSQLですので、忘れると、全体が一つのSQLとみなされちゃいます。
アノテーションとマクロの解説
-
:one
、:many
、:exec
、:execresult
以外のアノテーションは、以下に解説があります -
sqlc.arg
のようなマクロは、以下に解説があります
終わり
というわけで、sqlcの紹介でした。
今の所、なかなかいい感じではないかと思います。
動的な条件の作り方、Typeのoverrideについても、別で書いています。