Goでsqlcを作成する

に公開

Goでsqlcを作成する

GoのORMの一つとしてsqclがあります。
sqlcではORM独自の書き方ではなく、生のSQLで書くことができます

設定ファイルや生成のコマンドまで基本的なことを書いていきます

基本的な話

sqlc.yaml の設定項目

設定項目 説明
engine データベースエンジン(mysql、postgresql等)を指定
queries SQLクエリファイルの場所を指定
gen コード生成の設定を行う
emit コード生成時の出力形式や動作を制御

emitオプションの詳細

生成内容 説明
何を生成するか JSONタグ、インターフェース、プリペアドステートメント等
どのような形式で生成するか ポインタ型、パブリック関数、正確なテーブル名等
どのような動作にするか 空スライス返却、DB引数付きメソッド等
version: "2"
sql:
  - engine: "postgresql"
    queries: "queries.sql"
    schema: "../../db/migrations"
    gen:
      go:
        package: "sqlc"
        out: "gen"
        sql_package: "database/sql"
        emit_json_tags: true # JSONタグ生成
        emit_prepared_queries: false # プリペアドステートメント生成(あんまりちゃんとわかってない)
        emit_interface: false # インターフェース生成
        emit_exact_table_names: false # テーブル名そのまま使用
        emit_empty_slices: false # 空スライス返却
        emit_exported_queries: false # publicメソッド生成
        emit_result_struct_pointers: false # 結果構造体ポインタ返却
        emit_params_struct_pointers: false # パラメータ構造体ポインタ受取
        emit_methods_with_db_argument: false # DB引数付きメソッド生成

Get操作の実装

Get操作のクエリ定義

queries/notes.sqlにGet用のクエリを追加:
一応そのテーブルの全てのカラムを...みたいな表現もできます
しかしテーブル設計の部分を見ないと「*」が何かわからないのでちゃんと書いたほうがいいかもしれません

また-- name: GetNote :oneGetNoteの部分は自動生成したときのメソッドになるので命名を気をつけなければなりません

-- name: GetNote :one
SELECT * FROM notes WHERE note_id = ?;

-- name: GetAllNotes :many
SELECT * FROM notes;

生成される構造体

type Note struct {
    NoteID    int32          `json:"note_id"`
    Title     sql.NullString `json:"title"`
    Content   sql.NullString `json:"content"`
    CreatedAt sql.NullTime   `json:"created_at"`
    UpdatedAt sql.NullTime   `json:"updated_at"`
}

生成されるGet操作コード

// 単一のノートを取得
func (q *Queries) GetNote(ctx context.Context, noteID int32) (Note, error) {
    row := q.queryRow(ctx, q.getNoteStmt, getNote, noteID)
    var i Note
    err := row.Scan(
        &i.NoteID,
        &i.Title,
        &i.Content,
        &i.CreatedAt,
        &i.UpdatedAt,
    )
    return i, err
}

// テーブル全体を取得
func (q *Queries) GetAllNotes(ctx context.Context) ([]Note, error) {
    rows, err := q.query(ctx, q.getAllNotesStmt, getAllNotes)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    var items []Note
    for rows.Next() {
        var i Note
        if err := rows.Scan(
            &i.NoteID,
            &i.Title,
            &i.Content,
            &i.CreatedAt,
            &i.UpdatedAt,
        ); err != nil {
           return nil, err
        }
        items = append(items, i)
    }
    return items, nil
}

開発フロー

  1. スキーマ変更

    # マイグレーションファイルの作成・実行
    migrate create -ext sql -dir migrations add_notes_table
    migrate -database "mysql://user:pass@tcp(localhost:3306)/dbname" -path migrations up
    
  2. クエリ追加・修正

    # queries/notes.sqlを編集後
    sqlc generate
    

まとめ

今回は簡単なクエリに絞りましたが、多くの人がわかるSQLで書いた内容が自動生成でよしなにやってくれる点はかなりいいと思いました

生成する構造体のタグなども設定のboolで変更可能なので自由に決められる点などもありました
静的なクエリを生成するのであればかなり開発体験がいいですが、動的クエリの場合の書き方がまだそこまで網羅的にかけていないので色々触っていきます

参考ドキュメント

Discussion