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

## sqlcとは？

[sqlc](https://github.com/sqlc-dev/sqlc)は、Go のORMですが、少し変わっていて、自分でSQLを書きます。「SQLを書きたくない!」という人には、絶望的に向いてないですが、「ORMが作るSQLは気に食わない」とか、「結局どんなSQL生成されてるのか気になるんだけど」とか何度か思ったことのある人(「ORM名 SQLをログに出す」とかでググったことのある人)には割と気に入るかもしれません。

## sqlcを選んだ理由

今まで使ったORMは、順番でいくと、[sqlx](https://github.com/jmoiron/sqlx)(は、ORMではないか)、[Beego](https://github.com/beego/beego)のORM、[sqlboiler](https://github.com/volatiletech/sqlboiler)です。[gorm](https://github.com/go-gorm/gorm) はコードレビューでは見たことがありますが、書いたことはないです。

正直、プロジェクトを変えるたびに変えているので、どうこう述べるほど使い込んではいないのですよね。

* sqlx ... SQL書くのは同じっちゃおなじだけど、ちょっと
* BeegoのORM ... 今回、Beego使わないから
* sqlboiler ... 割とよかった印象
* gorm ... メジャーだけど使ったことないな

ということで、sqlboiler か gorm かというところで、それ以外の選択肢を調べて、sqlcってのがあって面白そう、という感じで選びました。あえて sqlc を選んだ理由としては、単純に「なんだかんだ、SQL書いてるほうがわかりやすい」というところかもしれません。要するに、前段ですでに述べた理由が全てですかね。

デメリットとして、動的な検索条件(WHERE)は作りにくいというか作れないということはありますが、自動生成されたコードはかなり素直なので、それを参考に自分で新たなメソッドを定義すればなんとでもなります([別で書いてます](https://qiita.com/ktat/items/ddf0e5e510279503feee))。

なお、利用しているデータベースは MySQL です。sqlc自体は当初PostgreSQL用で作られたとのことなので、PostgreSQLではできるけど、MySQLではできないということがいくつかあるようです。

## SQLの書き方

### 単純プレースホルダ

```sql
-- name: GetUser :one
SELECT * FROM user where id = ?;
```

単純なケースはこんな感じですね。PostgreSQLでは `?`の代わりに`$1` を使えるようですが、MySQLでは使えないようです。

### プレースホルダに名前をつける

以下のように、`sqlc.arg(user_id)`のようにすると、自動生成されるコードの変数名が`userID`となり、ちょっとわかりやすくなります。 

```sql
-- name: GetUserWithID :one
SELECT * FROM user where id = sqlc.arg(userID);
```

### `IN` を使う

`(?)` とか書いてもだめでして、`sqlc.slice(名前)`のように書きます。

```sql
-- name: GetUsersWithIDs :many
SELECT * FROM user where id in sqlc.slice(userIDs);
```

### INSERT/UPDATE

SQLの上のコメントの最後に、`:one`や `:many` というのがついていますが、アノテーションといいます。INSERTやUPDATEでは、`:exec`や`:execresult`を使います。`:execresult`を使うと、`sql.Result`が返ってきます。

```sql
-- name: UpdateUsreNameWithID :exec
UPDATE user SET NAME = ? WHERE id = ?;
```

### `SUM()`が`inteface`で返ってくる

単純に下記のように書くと、`sum(age)`は`interface`として返ってきます。

```sql
-- name: GetUserSumAge :one
SELECT sum(age) FROM user;
```

以下のようにSQLの中で`CAST`する必要があります。

```sql
-- name: GetUserSumAge :one
SELECT CAST(SUM(age) AS unsigned) sum_age FROM user;
```

こうすると、`uint64` で返ってきます。

### JOINする

```sql
-- name: GetUserWithItem :many
SELECT * FROM user JOIN user_item as ui ON user.id = ui.user_id;
```

このように書いたとすると、user テーブルと user_item テーブルが合成された、GetUserWithItemRowというSQLに対応して作られた型で返ってきます。イメージこんな感じです。

```go
type GetUserWithItemRow struct {
  ID int
  Name string
  // ...
  ID_2 int
  UserID int
  ItemID int
  // ...
}
```
`id`のような重複するカラム名は、上記のように`_2`がついてしまいます。

#### `AS`を使う

もちろん、カラムを書き出して`AS`で書けば、その名前にはなります。

```sql
-- 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;
```

とすれば、下記のような型になります。

```go
type GetUserWithItemRow struct {
  ID int
  Name string
  UserItemID int
}
```

#### `sqlc.embed`を使う

また、`*`で書いても良いなら、`sqlc.embed` マクロを使うのが便利です。

```sql
SELECT sqlc.embed(user), sqlc.embed(user_item) FROM user JOIN user_item as ui ON user.id = ui.user_id;
```

このようにすると、次のような typeで返ってきます。

```go
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`以外のアノテーションは、以下に解説があります
    - https://docs.sqlc.dev/en/stable/reference/query-annotations.html
- `sqlc.arg` のようなマクロは、以下に解説があります
    - https://docs.sqlc.dev/en/stable/reference/macros.html

## 終わり

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

[動的な条件の作り方](https://qiita.com/ktat/items/ddf0e5e510279503feee)、[Typeのoverride](https://qiita.com/ktat/items/74c9f78e96e57282fc5d)についても、別で書いています。