8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go のちょっと変わったORM sqlc の紹介

Last updated at Posted at 2024-08-21

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とみなされちゃいます。

アノテーションとマクロの解説

終わり

というわけで、sqlcの紹介でした。
今の所、なかなかいい感じではないかと思います。

動的な条件の作り方Typeのoverrideについても、別で書いています。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?