こんにちは。KOUKIです。
Go言語でアプリケーション開発手法を紹介しています。
前回は、マイグレーションについて触れました。
今日は、CRADについて学びたいと思います。
<目次>
CRUDとは
CRUDは、データ登録「Create」、読み込み「Read」、更新「Update」、削除「Delete」の略で、データベース操作を表します。
いよいよ、Go言語からPostgreSQLを操作する時がきたのです!
これができると自分でアプリケーションが作れるようになるので、頑張って覚えましょう!
事前準備
sqlcのインストール
kyleconroy/sqlcをインストールしましょう。
これは、SQL文をGo言語のコードに変換してくれるすごいツールです。
私はMac環境なので、次のコマンドでインストールします。
1 2 3 4 5 6 7 |
# install brew install kyleconroy/sqlc/sqlc # check version sqlc version v1.6.0 |
sqlcファイルの作成
次に「sqlc init」コマンドで、sqlcファイルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# 場所 /Users/hoge/go/src/github.com/hoge/golang-with-postgres # yamlファイルを作成 sqlc init # フォルダ作成 mkdir db/sqlc mkdir db/query # ディレクトリ確認 tree golang-with-postgres ├── Makefile ├── db │ ├── migration │ │ ├── 000001_init_schema.down.sql │ │ └── 000001_init_schema.up.sql │ ├── query │ └── sqlc ├── docker │ ├── golang │ │ └── Dockerfile │ └── postgres │ ├── Dockerfile │ └── Simple\ bank.sql ├── docker-compose.yml ├── go.mod ├── go.sum ├── main.go └── sqlc.yaml <<<<<<<< これができる |
このファイルには、コードの生成先や生成ルールを書くようです。基本的な設定は、「GitHubのREADME」にあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
version: "1" packages: - name: "db" # goのパッケージ名 path: "./db/sqlc" # sqlc格納 queries: "./db/query/" # query格納 schema: "./db/migration/" # スキーマー格納 engine: "postgresql" # DBタイプを洗濯 emit_json_tags: true # structにjsonのタグをつけるかいなか emit_prepared_queries: false # preparedをサポートするか emit_interface: false # Querierインターフェースを作るかどうか emit_exact_table_names: false # 構造体名にテーブル名を反映させるかどうか(book tableの場合、Book structが作成される) emit_empty_slices: false # manyクエリによって返却されるスライスは、nilでなく空にするか |
Create文(Insert)を追加
次に、query(Insert)を書きましょう。
1 |
touch db/query/account.sql |
このファイルに記述したSQL文を元にGoのコードを作成します。例えば、こんな風に書きます。
1 2 3 4 5 6 7 8 9 |
-- name: CreateAccount :one INSERT INTO accounts ( owner, balance, currency ) VALUES ( $1, $2, $3 ) RETURNING *; |
「— name: CreateAccount :one」の部分は、必須です。これがないとaccount.sql.goファイルが作成されません。
SQLからGoコードを生成
いよいよSQLからGoのコードを生成します。作成するためには、次のコマンドを実行するだけです。
1 2 |
# Goコード生成コマンド sqlc generate |
問題なく処理が完了したらdb/sqlc配下に以下のファイルが生成されているはずです。
1 2 3 4 5 |
$ tree db/sqlc/ db/sqlc/ ├── account.sql.go ├── db.go └── models.go |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// account.sql.go // Code generated by sqlc. DO NOT EDIT. // source: account.sql package db import ( "context" ) const createAccount = `-- name: CreateAccount :one INSERT INTO accounts ( owner, balance, currency ) VALUES ( $1, $2, $3 ) RETURNING id, owner, balance, currency, created_at ` type CreateAccountParams struct { Owner string `json:"owner"` Balance int64 `json:"balance"` Currency string `json:"currency"` } func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) { row := q.db.QueryRowContext(ctx, createAccount, arg.Owner, arg.Balance, arg.Currency) var i Account err := row.Scan( &i.ID, &i.Owner, &i.Balance, &i.Currency, &i.CreatedAt, ) return i, err } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// models.go // Code generated by sqlc. DO NOT EDIT. package db import ( "time" ) type Account struct { ID int64 `json:"id"` Owner string `json:"owner"` Balance int64 `json:"balance"` Currency string `json:"currency"` CreatedAt time.Time `json:"created_at"` } type Entry struct { ID int64 `json:"id"` AccountID int64 `json:"account_id"` // can be negative or positive Amount int64 `json:"amount"` CreatedAt time.Time `json:"created_at"` } type Transfer struct { ID int64 `json:"id"` FromAccountID int64 `json:"from_account_id"` ToAccountID int64 `json:"to_account_id"` // must be positive Amount int64 `json:"amount"` CreatedAt time.Time `json:"created_at"` } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 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, } } |
このコマンドをMakefileに追加しておきましょう。
1 2 3 4 5 6 |
# sqlc生成 sqlc: sqlc generate .PHONY: up createdb dropdb migrateup migratedown createsc sqlc |
Read文(Select)を追加
Select文を追加してみましょう。
1 2 3 4 5 6 7 8 9 |
-- name: GetAccount :one SELECT * FROM accounts WHERE id = $1 LIMIT 1; -- name: ListAccounts :many SELECT * FROM accounts ORDER BY id LIMIT $1 OFFSET $2; |
1 |
make sqlc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
const getAccount = `-- name: GetAccount :one SELECT id, owner, balance, currency, created_at FROM accounts WHERE id = $1 LIMIT 1 ` func (q *Queries) GetAccount(ctx context.Context, id int64) (Account, error) { row := q.db.QueryRowContext(ctx, getAccount, id) var i Account err := row.Scan( &i.ID, &i.Owner, &i.Balance, &i.Currency, &i.CreatedAt, ) return i, err } const listAccounts = `-- name: ListAccounts :many SELECT id, owner, balance, currency, created_at FROM accounts ORDER BY id LIMIT $1 OFFSET $2 ` type ListAccountsParams struct { Limit int32 `json:"limit"` Offset int32 `json:"offset"` } func (q *Queries) ListAccounts(ctx context.Context, arg ListAccountsParams) ([]Account, error) { rows, err := q.db.QueryContext(ctx, listAccounts, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() var items []Account for rows.Next() { var i Account if err := rows.Scan( &i.ID, &i.Owner, &i.Balance, &i.Currency, &i.CreatedAt, ); 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 } |
Update文を追加
次にUpdate文を追加しましょう。
1 2 3 4 5 |
-- name: UpdateAccount :one UPDATE accounts SET balance = $2 WHERE id = $1 RETURNING *; |
1 |
make sqlc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const updateAccount = `-- name: UpdateAccount :one UPDATE accounts SET balance = $2 WHERE id = $1 RETURNING id, owner, balance, currency, created_at ` type UpdateAccountParams struct { ID int64 `json:"id"` Balance int64 `json:"balance"` } func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) { row := q.db.QueryRowContext(ctx, updateAccount, arg.ID, arg.Balance) var i Account err := row.Scan( &i.ID, &i.Owner, &i.Balance, &i.Currency, &i.CreatedAt, ) return i, err } |
Delete文を追加
続いて、Delete文を追加しましょう。
1 2 3 |
-- name: DeleteAccount :exec DELETE FROM accounts WHERE id = $1; |
1 |
make sqlc |
1 2 3 4 5 6 7 8 9 |
const deleteAccount = `-- name: DeleteAccount :exec DELETE FROM accounts WHERE id = $1 ` func (q *Queries) DeleteAccount(ctx context.Context, id int64) error { _, err := q.db.ExecContext(ctx, deleteAccount, id) return err } |
次回
sqlcを使うとSQLからGo言語のプログラムを自動生成できるので、大変便利ですね!
次回は、CRUDのUnit Testについて学びましょう。
コメントを残す
コメントを投稿するにはログインしてください。