And lo, the Great One looked down upon the people and proclaimed:
"SQL is actually pretty great"
sqlc は、SQL から非常に型安全で使いやすい Go コードを生成します。以下にその方法を示します。 手順:
- SQL クエリを書きます
- sqlc を実行して、これらのクエリに型安全なインタフェースを提供する Go コードを生成します
- sqlc が生成したメソッドを呼び出すアプリケーションコードを書きます。
本当に、それはとても簡単です。もう二度と SQL クエリのボイラープレートコードを書く必要はありません。
sqlc は単にボイラプレートを生成して生産性を上げるだけではありません。 sqlc は SQL における一般的なエラーのほとんどを防止します。
あなたは今までに次のようなことを経験したことはありませんか?
- クエリを実行する際に引数の順番が間違っていたため、SQL 文とマッチしなかった
- あるクエリではカラム名を更新したが、他のクエリで更新するのを忘れた
- クエリ内のカラム名が間違っていた
- クエリの引数の数を変更したが、呼び出す際に追加の値を渡すのを忘れた
- カラムの型を変更したが、コードでは型を変更し忘れた
これらのエラーはすべて sqlc では 絶対にありえません 。待って、どういうこと?どうやって?
sqlc はコード生成プロセスの間にすべてのクエリと DDL 文(例: CREATE TABLE
)を解析します。つまり、テーブル内のすべてのカラムの名前、型、クエリ内のすべての式を知っています。それらのどれかが一致しない場合、sqlc は クエリのコンパイル に失敗します。実行時エラーをコンパイル時に防ぐことができます。
同じく、sqlc が生成するメソッドは、厳密なアリティとカラムにマッチする正しい Go 型定義を持っています。ですから、クエリの引数やカラムの型を変更して、コードは更新していない場合、コンパイルに失敗します。
誇大広告はもう終わりにして 実際に見てみましょう。
まず、以下の SQL をsqlc generate
コマンドに渡します:
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;
そして、以下のように、これを利用するコードを書きます
// authorsの一覧を取得
authors, err := db.ListAuthors(ctx)
if err != nil {
return err
}
fmt.Println(authors)
// authorを作成
insertedAuthor, err := db.CreateAuthor(ctx, db.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
return err
}
fmt.Println(insertedAuthor)
// 作成したauthorを取得する
fetchedAuthor, err := db.GetAuthor(ctx, insertedAuthor.ID)
if err != nil {
return err
}
// trueが出力される
fmt.Println(reflect.DeepEqual(insertedAuthor, fetchedAuthor))
このコードを実行可能にするために、sqlc は読みやすく、使いやすい Go コードを生成します。見てみてください。
package db
import (
"context"
"database/sql"
)
type Author struct {
ID int64
Name string
Bio sql.NullString
}
const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING id, name, bio
`
type CreateAuthorParams struct {
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
row := q.db.QueryRowContext(ctx, createAuthor, arg.Name, arg.Bio)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
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
}
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,
}
}
さまざまな PostgreSQL / Go の機能がサポートされています。
- SQL
- PostgreSQL Types
- DDL
- Go
エンドツーエンドの完全なサンプルは、[ondeck
] (./examples/ondeck) パッケージにあります
Usage:
sqlc [command]
Available Commands:
compile Statically check SQL for syntax and type errors
generate Generate Go code from SQL
help Help about any command
init Create an empty sqlc.yaml settings file
version Print the sqlc version number
Flags:
-h, --help help for sqlc
Use "sqlc [command] --help" for more information about a command.
sqlc
ツールは sqlc.yaml
または sqlc.json
を使って設定します。このファイルは sqlc
コマンドを実行するディレクトリに置く必要があります。
version: "1"
packages:
- name: "db"
path: "internal/db"
queries: "./sql/query/"
schema: "./sql/schema/"
engine: "postgresql"
emit_json_tags: true
emit_prepared_queries: true
emit_interface: false
emit_exact_table_names: false
emit_empty_slices: false
packages
ドキュメントには、以下のキーがあります。
name
:- 生成コードに使用するパッケージ名。デフォルトは
path
ベースネーム
- 生成コードに使用するパッケージ名。デフォルトは
path
:- 生成コードを出力するディレクトリ
queries
:- SQL クエリのディレクトリ。または単一の SQL ファイルへのパス。またはパスのリスト
schema
:- SQL スキーママイグレーションファイルのディレクトリ。または単一の SQL ファイルへのパス。またはパスのリスト
engine
:postgresql
またはmysql
のいずれかを指定します。デフォルトはpostgresql
です。MySQL のサポートは実験的なものです。
emit_json_tags
:- true の場合、生成された構造体に JSON タグを追加する。デフォルトは
false
- true の場合、生成された構造体に JSON タグを追加する。デフォルトは
emit_prepared_queries
:- true の場合、プリペアドクエリをサポートするようにする。デフォルトは
false
- true の場合、プリペアドクエリをサポートするようにする。デフォルトは
emit_interface
:- true の場合、生成されるパッケージに
Querier
インターフェースを出力する。デフォルトはfalse
- true の場合、生成されるパッケージに
emit_exact_table_names
:- true の場合、構造体名はテーブル名を反映します。そうでない場合、sqlc は複数のテーブル名を単数化しようとします。デフォルトは
false
- true の場合、構造体名はテーブル名を反映します。そうでない場合、sqlc は複数のテーブル名を単数化しようとします。デフォルトは
emit_empty_slices
:- true の場合
:many
クエリが返すスライスはnil
ではなく空になります。デフォルトはfalse
- true の場合
PostgreSQL タイプから Go タイプへのデフォルトのマッピングでは、必要な場合にのみ、標準ライブラリ以外のパッケージを使用します
たとえば、PostgreSQL のuuid
型はgithub.com/google/uuid
にマップされます
UUID 用の別の Go パッケージが必要な場合は、overrides
配列にパッケージを指定します。この場合、代わりに github.com/gofrs/uuid
が使われます
version: "1"
packages: [...]
overrides:
- go_type: "github.com/gofrs/uuid.UUID"
db_type: "uuid"
overrrides
ドキュメントは以下のキーを持ちます:
db_type
:- オーバーライドする PostgreSQL の型。サポートされている型の一覧は gen.goを参照してください
go_type
:- 生成されたコードで使用する Go の型の完全修飾名。
nullable
:- true の場合、この型はカラムがヌル可能な場合に使用される。デフォルトは
false
- true の場合、この型はカラムがヌル可能な場合に使用される。デフォルトは
前のセクションで説明したようにタイプベースではなく、テーブルの特定のフィールドに対して、モデルまたはクエリ生成で使用される Go のタイプをオーバーライドしたい場合があります。
これは、override 定義で column
プロパティを指定することで設定できます。column
は table.column
の形式でなければなりませんが、schema.table.column
や catalog.schema.table.column
を指定することでさらに指定することができます。
version: "1"
packages: [...]
overrides:
- column: "authors.id"
go_type: "github.com/segmentio/ksuid.KSUID"
前のセクションで説明したように、オーバーライドはグローバルに設定できます。 パッケージごとに設定することも可能で、オーバーライドの動作を単一のパッケージだけにスコープすることもできます。
version: "1"
packages:
- overrides: [...]
構造フィールド名は、以下のような単純なアルゴリズムを使用してカラム名から生成されます:
アンダースコアでカラム名を分割し、各部分の最初の文字を大文字にします。
account -> Account
spotify_url -> SpotifyUrl
app_id -> AppID
生成されたフィールド名に不満がある場合は、rename
辞書を使って新しい名前を選ぶことができます。キーはカラム名、値は使用する構造体フィールド名です
version: "1"
packages: [...]
rename:
spotify_url: "SpotifyURL"
brew install kyleconroy/sqlc/sqlc
sudo snap install sqlc
go get github.com/kyleconroy/sqlc/cmd/sqlc
docker pull kjconroy/sqlc
Run sqlc
using docker run
:
docker run --rm -v $(pwd):/src -w /src kjconroy/sqlc generate
Binaries for a given release can be downloaded from the stable channel on Equinox or the latest GitHub release.
Each commit is deployed to the devel
channel on Equinox:
sqlc は現在 PostgreSQL / Go のみをサポートしています。MySQL と Kotlin のサポートはマージされていますが、どちらも実験的なものとしてマークされています。SQLite と TypeScript のサポートが予定されています。
Language | PostgreSQL | MySQL |
---|---|---|
Go | ✅ - Stable | 🐛 - Beta |
TypeScript | ⏲️ - Planned | ⏲️ - Planned |
Kotlin |
他のデータベースや言語を追加したいなら、私たちは貢献を歓迎します。
sqlc development is funded by our generous sponsors.
- Companies
- Individuals
If you use sqlc at your company, please consider becoming a sponsor today.
Sponsors receive priority support via the sqlc Slack organization.
For local development, install sqlc
under an alias. We suggest sqlc-dev
.
go build -o ~/go/bin/sqlc-dev ./cmd/sqlc
To run the tests, include the exp
tag. Without this tag, a few tests will
fail.
go test --tags=exp ./...
To run the tests in the examples folder, a running PostgreSQL instance is required. The tests use the following environment variables to connect to the database
Variable Default Value
-------------------------
PG_HOST 127.0.0.1
PG_PORT 5432
PG_USER postgres
PG_PASSWORD mysecretpassword
PG_DATABASE dinotest
Variable Default Value
-------------------------
MYSQL_HOST 127.0.0.1
MYSQL_PORT 3306
MYSQL_USER root
MYSQL_ROOT_PASSWORD mysecretpassword
MYSQL_DATABASE dinotest
go test --tags=examples,exp ./...
If you need to update a large number of expected test output in the internal/endtoend/testdata
directory, run the regenerate.sh
script.
make regen
Note that this uses the sqlc-dev
binary, not sqlc
so make sure you have an up to date sqlc-dev
binary.