sqlc(DBクエリ生成)¶
概要¶
本プロジェクトでは sqlc を使って SQL クエリから型安全な Go コードを自動生成している。
手書きの database/sql コードを排除し、SQLインジェクションの防止とコンパイル時の型チェックを実現する。
ディレクトリ構成¶
app/
├── sqlc.yaml # sqlc 設定ファイル
├── sql/
│ └── queries/ # SQL クエリ定義
│ ├── questions.sql # 問題関連
│ ├── categories.sql # カテゴリ関連
│ ├── workbooks.sql # 問題集関連
│ ├── images.sql # 画像関連
│ ├── answers.sql # 回答履歴関連
│ └── importer.sql # データインポート用
├── internal/
│ └── db/ # 生成されたコード(編集禁止)
│ ├── db.go # DB接続・トランザクション
│ ├── models.go # テーブルモデル
│ ├── querier.go # インターフェース定義
│ ├── questions.sql.go
│ ├── categories.sql.go
│ ├── workbooks.sql.go
│ ├── images.sql.go
│ ├── answers.sql.go
│ └── importer.sql.go
コード生成¶
cd app
go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate
internal/db/ 配下のファイルが再生成される。生成コードは直接編集しないこと。
設定(sqlc.yaml)¶
version: "2"
sql:
- engine: "postgresql"
queries: "sql/queries" # クエリ定義ディレクトリ
schema: "../migrations" # マイグレーションファイルをスキーマとして使用
gen:
go:
package: "db"
out: "internal/db"
sql_package: "database/sql"
emit_json_tags: true
emit_interface: true # Querier インターフェース生成
emit_empty_slices: true # nil ではなく空スライスを返す
- schema: マイグレーションファイル(
migrations/*.up.sql)をスキーマ定義として使用。別途スキーマファイルを管理する必要がない - emit_interface:
Querierインターフェースが生成され、テストでのモック差し替えが容易になる
クエリの書き方¶
基本構文¶
-- name: GetQuestionByID :one
SELECT q.id, qsc.text, qsc.explanation
FROM questions q
JOIN questions_single_choice qsc ON q.id = qsc.question_id
WHERE q.id = $1;
| アノテーション | 意味 |
|---|---|
:one |
1行を返す(QueryRowContext) |
:many |
複数行を返す(QueryContext) |
:exec |
結果を返さない(ExecContext) |
:execresult |
sql.Result を返す(ExecContext) |
配列パラメータ¶
PostgreSQL の ANY を使用する:
-- name: GetImageURLsByQuestionIDs :many
SELECT question_id, path
FROM question_images qi
JOIN images i ON qi.image_id = i.id
WHERE qi.question_id = ANY($1::bigint[])
ORDER BY qi.question_id, qi.order_index;
Go 側では []int64 として渡せる。pq.Array は不要。
使い方¶
基本¶
queries := db.New(sqlDB)
row, err := queries.GetQuestionByID(ctx, questionID)
トランザクション¶
WithTx で同一トランザクション上のクエリオブジェクトを作成する:
tx, err := sqlDB.Begin()
defer tx.Rollback()
qtx := queries.WithTx(tx)
qtx.CreateQuestion(ctx, ...)
qtx.CreateSingleChoice(ctx, ...)
tx.Commit()
admin handler と importer がこのパターンを使用している。
クエリファイル一覧¶
| ファイル | 内容 |
|---|---|
questions.sql |
問題の CRUD、選択肢の取得・作成・更新・削除 |
categories.sql |
カテゴリの CRUD、カテゴリ別問題集取得 |
workbooks.sql |
問題集の CRUD、問題集内の問題取得(選択肢JOIN版含む) |
images.sql |
画像URLの取得(単一・複数問題ID対応)、画像の作成・紐付け |
answers.sql |
ユーザーの Upsert、回答記録、間違えた問題の取得 |
importer.sql |
全テーブルの DELETE、明示的ID指定での INSERT |
クエリを追加・変更する手順¶
app/sql/queries/*.sqlにクエリを追加・編集cd app && go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generateで再生成internal/db/の生成コードをコミット- handler 等で新しいクエリを使用
スキーマ変更(カラム追加等)がある場合は、先にマイグレーションファイルを作成すること。sqlc はマイグレーションファイルをスキーマとして読み取る。