sqlc: SQLからGoコードを自動生成


[![Group152.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/3a007459-9fec-488b-a26d-39e16cc53823.png)](https://leapcell.io/?lc_t=q_sqlc)


**[Leapcell: The Next-Gen Serverless Platform for Golang Hosting](https://leapcell.io/?lc_t=q_sqlc)**

### はじめに
Go言語の`database/sql`標準ライブラリが提供するインターフェイスは比較的低レベルです。これにより、私たちは大量の繰り返しコードを書く必要があります。この多くの定型コードは、書くのが面倒ばかりでなく、エラーが発生しやすいものです。時には、フィールドの型を変更すると、たくさんの場所で修正が必要になる場合があります。新しいフィールドを追加する場合も、以前に`select *`クエリステートメントを使用していた場所を修正する必要があります。何か漏れがあると、実行時にパニックが発生する可能性があります。ORMライブラリを使用したとしても、これらの問題を完全に解決することはできません！そこでsqlcが登場です！sqlcは、私たちが書くSQLステートメントに基づいて、型安全で慣習的なGoのインターフェイスコードを生成することができ、私たちはただこれらのメソッドを呼び出すだけです。

### クイックスタート
#### インストール
まず、sqlcをインストールします：
```bash
$ go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
```
もちろん、対応するデータベースドライバも必要です：
```bash
$ go get github.com/lib/pq
$ go get github.com/go-sql-driver/mysql
```

#### SQLステートメントの書き方
テーブル作成ステートメントを書きます。`schema.sql`ファイルに次の内容を書きます：
```sql
CREATE TABLE users (
  id   BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  bio  TEXT
);
```
クエリステートメントを書きます。`query.sql`ファイルに次の内容を書きます：
```sql
-- name: GetUser :one
SELECT * FROM users
WHERE id = $1 LIMIT 1;

-- name: ListUsers :many
SELECT * FROM users
ORDER BY name;

-- name: CreateUser :exec
INSERT INTO users (
  name, bio
) VALUES (
  $1, $2
)
RETURNING *;

-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1;
```
sqlcはPostgreSQLをサポートしています。sqlcは小さな設定ファイル`sqlc.yaml`だけを必要とします：
```yaml
version: "1"
packages:
  - name: "db"
    path: "./db"
    queries: "./query.sql"
    schema: "./schema.sql"
```
**設定の説明**：
- `version`：バージョン。
- `packages`：
  - `name`：生成されるパッケージ名。
  - `path`：生成されるファイルのパス。
  - `queries`：クエリSQLファイル。
  - `schema`：テーブル作成SQLファイル。

#### Goコードの生成
次のコマンドを実行して、対応するGoコードを生成します：
```bash
sqlc generate
```
sqlcは同じディレクトリにデータベース操作コードを生成します。ディレクトリ構造は次のとおりです：
```
db
├── db.go
├── models.go
└── query.sql.go
```
sqlcは`schema.sql`と`query.sql`に基づいてモデルオブジェクトの構造を生成します：
```go
// models.go
type User struct {
  ID   int64
  Name string
  Bio  sql.NullString
}
```
そして操作インターフェイス：
```go
// query.sql.go
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
func (q *Queries) DeleteUser(ctx context.Context, id int64) error
func (q *Queries) GetUser(ctx context.Context, id int64) (User, error)
func (q *Queries) ListUsers(ctx context.Context) ([]User, error)
```
このうち、`Queries`はsqlcによってカプセル化された構造体です。

#### 使用例
```go
package main

import (
  "database/sql"
  "fmt"
  "log"

  _ "github.com/lib/pq"
  "golang.org/x/net/context"

  "github.com/leapcell/examples/sqlc"
)

func main() {
  pq, err := sql.Open("postgres", "dbname=sqlc sslmode=disable")
  if err != nil {
    log.Fatal(err)
  }

  queries := db.New(pq)

  users, err := queries.ListUsers(context.Background())
  if err != nil {
    log.Fatal("ListUsers error:", err)
  }
  fmt.Println(users)

  insertedUser, err := queries.CreateUser(context.Background(), db.CreateUserParams{
    Name: "Rob Pike",
    Bio:  sql.NullString{String: "Co-author of The Go Programming Language", Valid: true},
  })
  if err != nil {
    log.Fatal("CreateUser error:", err)
  }
  fmt.Println(insertedUser)

  fetchedUser, err := queries.GetUser(context.Background(), insertedUser.ID)
  if err != nil {
    log.Fatal("GetUser error:", err)
  }
  fmt.Println(fetchedUser)

  err = queries.DeleteUser(context.Background(), insertedUser.ID)
  if err != nil {
    log.Fatal("DeleteUser error:", err)
  }
}
```
生成されたコードは`db`パッケージの下にあります（`packages.name`オプションで指定されます）。まず、`db.New()`を呼び出し、`sql.Open()`の返り値`sql.DB`をパラメータとして渡して`Queries`オブジェクトを取得します。私たちが`users`テーブルに対するすべての操作は、このオブジェクトのメソッドを通じて完了する必要があります。

#### PostgreSQLの起動とデータベースおよびテーブルの作成
上記のプログラムを実行するには、またPostgreSQLを起動し、データベースとテーブルを作成する必要があります：
```bash
$ createdb sqlc
$ psql -f schema.sql -d sqlc
```
最初のコマンドは`sqlc`という名前のデータベースを作成し、2番目のコマンドは`sqlc`データベースで`schema.sql`ファイルのステートメントを実行し、つまりテーブルを作成します。

#### プログラムの実行
```bash
$ go run .
```
実行結果の例：
```
[]
{1 Rob Pike {Co-author of The Go Programming Language true}}
```

### コード生成
sqlcは、SQLステートメント自体に加えて、私たちがSQLステートメントを書く際にコメントの形式で生成されるプログラムに必要ないくつかの基本情報を提供する必要があります。構文は次のとおりです：
```sql
-- name: <name> <cmd>
```
`name`は生成されるメソッドの名前で、例えば上記の`CreateUser`、`ListUsers`、`GetUser`、`DeleteUser`などです。`cmd`には次の値があります：
- `:one`：SQLステートメントが1つのオブジェクトを返すことを示し、生成されるメソッドの返り値は`(オブジェクト型, error)`で、オブジェクト型はテーブル名から導き出すことができます。
- `:many`：SQLステートメントが複数のオブジェクトを返すことを示し、生成されるメソッドの返り値は`([]オブジェクト型, error)`です。
- `:exec`：SQLステートメントがオブジェクトを返さず、ただ`error`を返すことを示します。
- `:execrows`：SQLステートメントが影響を受けた行数を返す必要があることを示します。

#### `:one`の例
```sql
-- name: GetUser :one
SELECT id, name, bio FROM users
WHERE id = $1 LIMIT 1
```
コメントの`--name`は`GetUser`メソッドを生成するよう指示します。テーブル名から導き出すと、返り値の基本型は`User`です。`:one`はただ1つのオブジェクトが返されることを示します。したがって、最終的な返り値は`(User, error)`です：
```go
// db/query.sql.go
const getUser = `-- name: GetUser :one
SELECT id, name, bio FROM users
WHERE id = $1 LIMIT 1
`

func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) {
  row := q.db.QueryRowContext(ctx, getUser, id)
  var i User
  err := row.Scan(&i.ID, &i.Name, &i.Bio)
  return i, err
}
```

#### `:many`の例
```sql
-- name: ListUsers :many
SELECT * FROM users
ORDER BY name;
```
コメントの`--name`は`ListUsers`メソッドを生成するよう指示します。テーブル名`users`から導き出すと、返り値の基本型は`User`です。`:many`はオブジェクトのスライスが返されることを示します。したがって、最終的な返り値は`([]User, error)`です：
```go
// db/query.sql.go
const listUsers = `-- name: ListUsers :many
SELECT id, name, bio FROM users
ORDER BY name
`

func (q *Queries) ListUsers(ctx context.Context) ([]User, error) {
  rows, err := q.db.QueryContext(ctx, listUsers)
  if err != nil {
    return nil, err
  }
  defer rows.Close()
  var items []User
  for rows.Next() {
    var i User
    if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
      return nil, err
    }
    items = append(items, i)
  }
  if err := rows.Close(); err != nil {
    return nil, err
  }
  if err := rows.Err(); err != nil {
    return nil, err
  }
  return items, nil
}
```
ここで注意するべき詳細は、私たちが`select *`を使用しても、生成されたコードのSQLステートメントは特定のフィールドに書き換えられるということです：
```sql
SELECT id, name, bio FROM users
ORDER BY name
```
このように、後でフィールドを追加または削除する必要がある場合、`sqlc`コマンドを実行するだけで、このSQLステートメントと`ListUsers()`メソッドを一致させることができ、非常に便利です！

#### `:exec`の例
```sql
-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1
```
コメントの`--name`は`DeleteUser`メソッドを生成するよう指示します。テーブル名`users`から導き出すと、返り値の基本型は`User`です。`:exec`はオブジェクトが返されないことを示します。したがって、最終的な返り値は`error`です：
```go
// db/query.sql.go
const deleteUser = `-- name: DeleteUser :exec
DELETE FROM users
WHERE id = $1
`

func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
  _, err := q.db.ExecContext(ctx, deleteUser, id)
  return err
}
```

#### `:execrows`の例
```sql
-- name: DeleteUserN :execrows
DELETE FROM users
WHERE id = $1
```
コメントの`--name`は`DeleteUserN`メソッドを生成するよう指示します。テーブル名`users`から導き出すと、返り値の基本型は`User`です。`:exec`は影響を受けた行数（つまり削除された行数）が返されることを示します。したがって、最終的な返り値は`(int64, error)`です：
```go
// db/query.sql.go
const deleteUserN = `-- name: DeleteUserN :execrows
DELETE FROM users
WHERE id = $1
`

func (q *Queries) DeleteUserN(ctx context.Context, id int64) (int64, error) {
  result, err := q.db.ExecContext(ctx, deleteUserN, id)
  if err != nil {
    return 0, err
  }
  return result.RowsAffected()
}
```
書かれたSQLがどれほど複雑であっても、上記の規則に従います。私たちはSQLステートメントを書く際に、1行のコメントを追加するだけで、sqlcが私たちのために慣習的なSQL操作メソッドを生成してくれます。生成されたコードは、私たちが手で書くものと変わりありませんし、エラーハンドリングも非常に完全であり、手で書く際の面倒とエラーをも回避します。

### モデルオブジェクト
sqlcは、すべてのテーブル作成ステートメントに対して対応するモデル構造を生成します。構造名はテーブル名の単数形で、先頭文字は大文字になります。例えば：
```sql
CREATE TABLE users (
  id   SERIAL PRIMARY KEY,
  name text   NOT NULL
);
```
これに対応する構造を生成します：
```go
type User struct {
  ID   int
  Name string
}
```
また、sqlcは`ALTER TABLE`ステートメントを解析することができ、最終的なテーブル構造に基づいてモデルオブジオブジェクトの構造を生成します。例えば：
```sql
CREATE TABLE users (
  id          SERIAL PRIMARY KEY,
  birth_year  int    NOT NULL
);

ALTER TABLE users ADD COLUMN bio text NOT NULL;
ALTER TABLE users DROP COLUMN birth_year;
ALTER TABLE users RENAME TO writers;
```
上記のSQLステートメントでは、テーブルを作成するときに`id`と`birth_year`の2つのカラムがあります。最初の`ALTER TABLE`ステートメントは`bio`カラムを追加し、2番目は`birth_year`カラムを削除し、3番目はテーブル名を`users`から`writers`に変更します。sqlcは、最終的なテーブル名`writers`とテーブル内の`id`と`bio`のカラムに基づいてコードを生成します：
```go
package db

type Writer struct {
  ID  int
  Bio string
}
```

### 設定フィールド
`sqlc.yaml`ファイルでは、他の設定フィールドも設定することができます。

#### `emit_json_tags`
デフォルト値は`false`です。このフィールドを`true`に設定すると、生成されるモデルオブジェクト構造にJSONタグを追加することができます。例えば：
```sql
CREATE TABLE users (
  id         SERIAL    PRIMARY KEY,
  created_at timestamp NOT NULL
);
```
これにより、次のように生成されます：
```go
package db

import (
  "time"
)

type User struct {
  ID        int       `json:"id"`
  CreatedAt time.Time `json:"created_at"`
}
```

#### `emit_prepared_queries`
デフォルト値は`false`です。このフィールドを`true`に設定すると、SQLに対応するプリペアドステートメントが生成されます。例えば、クイックスタートの例でこのオプションを設定すると、最終的に生成される構造体`Queries`には、SQLに対応するすべてのプリペアドステートメントオブジェクトが追加されます：
```go
type Queries struct {
  db                DBTX
  tx                *sql.Tx
  createUserStmt    *sql.Stmt
  deleteUserStmt    *sql.Stmt
  getUserStmt       *sql.Stmt
  listUsersStmt     *sql.Stmt
}
```
そして、`Prepare()`メソッドが生成されます：
```go
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
  q := Queries{db: db}
  var err error
  if q.createUserStmt, err = db.PrepareContext(ctx, createUser); err != nil {
    return nil, fmt.Errorf("error preparing query CreateUser: %w", err)
  }
  if q.deleteUserStmt, err = db.PrepareContext(ctx, deleteUser); err != nil {
    return nil, fmt.Errorf("error preparing query DeleteUser: %w", err)
  }
  if q.getUserStmt, err = db.PrepareContext(ctx, getUser); err != nil {
    return nil, fmt.Errorf("error preparing query GetUser: %w", err)
  }
  if q.listUsersStmt, err = db.PrepareContext(ctx, listUsers); err != nil {
    return nil, fmt.Errorf("error preparing query ListUsers: %w", err)
  }
  return &q, nil
}
```
その他の生成されたメソッドはすべて、これらのオブジェクトを使用し、直接SQLステートメントを使用しません：
```go
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
  row := q.queryRow(ctx, q.createUserStmt, createUser, arg.Name, arg.Bio)
  var i User
  err := row.Scan(&i.ID, &i.Name, &i.Bio)
  return i, err
}
```
私たちは、プログラムの初期化時にこの`Prepare()`メソッドを呼び出す必要があります。

#### `emit_interface`
デフォルト値は`false`です。このフィールドを`true`に設定すると、クエリ構造体に対するインターフェイスが生成されます。例えば、クイックスタートの例でこのオプションを設定すると、最終的に生成されたコードには、追加のファイル`querier.go`があります：
```go
// db/querier.go
type Querier interface {
  CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
  DeleteUser(ctx context.Context, id int64) error
  DeleteUserN(ctx context.Context, id int64) (int64, error)
  GetUser(ctx context.Context, id int64) (User, error)
  ListUsers(ctx context.Context) ([]User, error)
}
```

### 結論
sqlcはまだ完全ではありませんが、確かにGoでのデータベースコードの書き方の複雑さを大幅に単純化し、私たちのコーディング効率を向上させ、エラーの発生率を減らすことができます。PostgreSQLを使用する人には、是非試してみることをおすすめします！

### 参考文献
- [https://github.com/sqlc-dev/sqlc](https://github.com/sqlc-dev/sqlc)

# [Leapcell: The Next-Gen Serverless Platform for Golang Hosting](https://leapcell.io/?lc_t=q_sqlc)
最後に、Goサービスのデプロイに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/?lc_t=q_sqlc)**

[![barndpic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f7bc5c4d-c5ff-42db-b376-9aeba4c94f38.png)](https://leapcell.io/?lc_t=q_sqlc)


### 1. 多言語対応  
- JavaScript、Python、Go、またはRustで開発できます。  

### 2. 無料で無制限のプロジェクトをデプロイ
- 使用した分だけ支払います — リクエストがなければ、請求はありません。

### 3. 抜群のコスト効率  
- 使った分だけ支払い、アイドル時には請求されません。  
- 例：平均応答時間60msで694万件のリクエストに対応するのに25ドルです。  

### 4. 洗練された開発者体験  
- 直感的なUIで簡単にセットアップできます。  
- 完全自動化されたCI/CDパイプラインとGitOpsの統合。  
- 実時間のメトリクスとロギングによる実行可能なインサイト。  

### 5. 簡単なスケーラビリティと高パフォーマンス  
- 高い同時実行性を簡単に処理できる自動スケーリング。  
- ゼロの運用オーバーヘッド — ただ構築に集中できます。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/943bb892-b301-4c7e-aec6-12e56dfeacc5.png)](https://leapcell.io/?lc_t=q_sqlc)


[ドキュメントでさらに詳しく調べる！](https://docs.leapcell.io/)

LeapcellのTwitter：[https://x.com/LeapcellHQ](https://x.com/LeapcellHQ)