kyleconroy/sqlc はSQLからGoのコードを生成してくれるツールです。
schemaやSELECT文などからコードを生成してくれます。
今回は基本的な使い方を紹介します。
インストール
$ go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
brewでもインストール可能です。
使い方
設定ファイル、schema、queryの準備
sqlcを使うために3つのファイルを準備します。
. ├── query.sql ├── schema.sql └── sqlc.yml
# sqlc.yml version: 1 packages: - path: "db" name: "db" # package name engine: "postgresql" schema: "schema.sql" queries: "query.sql"
-- schema.sql CREATE TABLE users ( id uuid NOT NULL PRIMARY KEY, name text NOT NULL );
Get, Create, Update, Delete, List, CountのSQLを書きました。
これらのSQLからコードを生成することになります。
-- query.sql -- name: Get :one SELECT * FROM users WHERE id = $1 LIMIT 1; -- name: Create :one INSERT INTO users ( id, name ) VALUES ( $1, $2 ) RETURNING *; -- name: Update :exec UPDATE users SET name = $2 WHERE id = $1; -- name: Delete :exec DELETE FROM users WHERE id = $1; -- name: List :many SELECT * FROM users ORDER BY id; -- name: Count :one SELECT count(*) FROM users;
コードを生成する
$ sqlc generate -f sqlc.yml
db下にファイルが生成されました。
. ├── db (生成されたファイル) │ ├── db.go │ ├── models.go │ └── query.sql.go ├── query.sql ├── schema.sql └── sqlc.yml
models.go
// Code generated by sqlc. DO NOT EDIT. package db import ( "github.com/google/uuid" ) type User struct { ID uuid.UUID Name string }
db.go
// Code generated by sqlc. DO NOT EDIT. package db import ( "context" "database/sql" ) type DBTX interface { ExecContext(context.Context, string, ...interface{}) (sql.Result, error) PrepareContext(context.Context, string) (*sql.Stmt, error) QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { return &Queries{db: db} } type Queries struct { db DBTX } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } }
query.sql.go
// Code generated by sqlc. DO NOT EDIT. // source: query.sql package db import ( "context" "github.com/google/uuid" ) const count = `-- name: Count :one SELECT count(*) FROM users ` func (q *Queries) Count(ctx context.Context) (int64, error) { row := q.db.QueryRowContext(ctx, count) var count int64 err := row.Scan(&count) return count, err } const create = `-- name: Create :one INSERT INTO users ( id, name ) VALUES ( $1, $2 ) RETURNING id, name ` type CreateParams struct { ID uuid.UUID Name string } func (q *Queries) Create(ctx context.Context, arg CreateParams) (User, error) { row := q.db.QueryRowContext(ctx, create, arg.ID, arg.Name) var i User err := row.Scan(&i.ID, &i.Name) return i, err } const delete = `-- name: Delete :exec DELETE FROM users WHERE id = $1 ` func (q *Queries) Delete(ctx context.Context, id uuid.UUID) error { _, err := q.db.ExecContext(ctx, delete, id) return err } const get = `-- name: Get :one SELECT id, name FROM users WHERE id = $1 LIMIT 1 ` func (q *Queries) Get(ctx context.Context, id uuid.UUID) (User, error) { row := q.db.QueryRowContext(ctx, get, id) var i User err := row.Scan(&i.ID, &i.Name) return i, err } const list = `-- name: List :many SELECT id, name FROM users ORDER BY id ` func (q *Queries) List(ctx context.Context) ([]User, error) { rows, err := q.db.QueryContext(ctx, list) 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); 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 } const update = `-- name: Update :exec UPDATE users SET name = $2 WHERE id = $1 ` type UpdateParams struct { ID uuid.UUID Name string } func (q *Queries) Update(ctx context.Context, arg UpdateParams) error { _, err := q.db.ExecContext(ctx, update, arg.ID, arg.Name) return err }
生成されたコードを使う
. ├── app.go ├── db │ ├── db.go │ ├── models.go │ └── query.sql.go ├── query.sql ├── schema.sql └── sqlc.yml
app.goに生成されたコードを使う部分を書いていくとこんな感じになります(このままでは動かないですが、雰囲気は伝わるかと思います)
WithTx() を使うことでトランザクションも使用可能です。
// app.go func createUser(ctx context.Context) error { postgresDB, err := sql.Open("postgres", "") if err != nil { return err } queries := db.New(postgresDB) tx, err := postgresDB.BeginTx(ctx, nil) if err != nil { return err } args := db.CreateParams{ ID: "00000000-0000-0000-0000-000000000000", Name: "inari111", } if _, err := queries.WithTx(tx).Create(ctx, args); err != nil { return err } if err := tx.Commit(); err != nil { return err } return nil }
使ってみてどうだったか
- SQLからGoのコードを生成できるのは便利だと思った
- NOT NULL制約をつけなければ
sql.NullString
でモデルの生成された sqlc compile
で構文や型のエラーをチェックすることも可能- k0kubun/sqldef で冪等にschema管理管理をし、sqlcでコードを生成する。といった使い方ができそうだと思った
- こういった処理はどれも似たようなコードになるので、自動生成することで開発時間の短縮につながりそう