Go の ORM ツールの一つである sqlc を使用する機会があったので、sqlc を使って、簡単なデータ取得のコードを記述するところまでを書き留めていきます。
バージョン
Mac で下記バージョンにて動作確認を行なっています。
対象 | バージョン |
---|---|
PostgreSQL | 14 |
Go | 1.21.1 |
sqlc | 1.23.0 |
sqlc とは
sqlc とは Go の ORM の一つです。
ORM とは、Object-Relational Mapping(オブジェクト関係マッピング)のことで、オブジェクトと RDB(Relational Database)のマッピングを行うものとなります。
ORMを使用することで、SQL を直接書くことなく、オブジェクトのメソッドで DB を操作することができます。
sqlc の初期設定
データベース操作を行う事前準備として、 sqlcの初期設定を行います。
1. sqlc.yaml を作成する
下記コマンドを実行し、sqlc.yaml を作成します。
$ sqlc init
2. sqlc.yaml を記述します
version: "1"
packages:
- name: "db"
path: "./db/sqlc/"
queries: "./db/query/"
schema: "./db/migrations/"
engine: "postgresql"
emit_json_tags: true
emit_exact_table_names: false
emit_empty_slices: true
項目 | 説明 |
---|---|
version | バージョン |
name | 自動生成されるコードのパッケージ名を設定 |
path | 自動生成されるコードの格納先のディレクトリを設定 |
queries | 実行する SQL 文をの格納先のディレクトリを設定 |
schema | マイグレーションファイルの格納先のディレクトリを設定 |
engine | 使用する DB 名を設定。mysql またはpostgresql
|
emit_json_tags | 自動生成されるコード内のモデル定義の構造体に json マッピング用のタグを付与するか設定 |
emit_exact_table_names | DB のテーブル名を単数形に直した名称をモデル定義の構造体の名前とするかを設定 |
emit_empty_slices | 複数レコードを取得するクエリを実行したとき、取得数が 0 件の場合、true: 空のスライス、false: nil を返却する |
プリペアードステートメントを作成・queries に指定した格納先に設置
プリペアードステートメントとは
SQL 文を動的に生成したいとき、変更する箇所を変数のようにした SQL 文を作成し、値の挿入をプログラムで行う仕組みのことをいいます。
プリペアードステートメント例
users テーブルからデータを取得するプリペアードステートメントを作成します。
users テーブルに対しユーザー作成(CreateUser)をするプリペアードステートメントと、users テーブルからメールアドレスでユーザーを取得する(GetUserByEmail)プリペアードステートメントを作成します。
-- name: CreateUser :one
INSERT INTO users (
user_name, email, phone_number, password
) VALUES (
$1, $2, $3, $4
) RETURNING *;
-- name: GetUserByEmail :one
SELECT
*
FROM
users
WHERE
email = $1;
※RETURNING *
を用いて、ユーザー作成時、登録した内容を返すようにします
プリペアードステートメントに基づき、Go コード生成
下記コマンドを実行すると、./db/sqlc
フォルダに Go コード作成されます。
$ sqlc generate
生成されたコード
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.23.0
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,
}
}
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.23.0
package db
import (
"database/sql"
)
type User struct {
ID int64 `json:"id"`
UserName string `json:"user_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Password sql.NullString `json:"password"`
}
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.23.0
// source: user.sql
package db
import (
"context"
"database/sql"
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (
user_name, email, phone_number, password
) VALUES (
$1, $2, $3, $4
) RETURNING id, user_name, email, phone_number, password
`
type CreateUserParams struct {
UserName string `json:"user_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
Password sql.NullString `json:"password"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, createUser,
arg.UserName,
arg.Email,
arg.PhoneNumber,
arg.Password,
)
var i User
err := row.Scan(
&i.ID,
&i.UserName,
&i.Email,
&i.PhoneNumber,
&i.Password,
)
return i, err
}
const getUserByEmail = `-- name: GetUserByEmail :one
SELECT
id, user_name, email, phone_number, password
FROM
users
WHERE
email = $1
`
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByEmail, email)
var i User
err := row.Scan(
&i.ID,
&i.UserName,
&i.Email,
&i.PhoneNumber,
&i.Password,
)
return i, err
}
データ作成と取得
生成されたコードを用いて、データの作成と、取得を行います。
package main
import (
"context"
"database/sql"
"fmt"
db "golang-qiita/db/sqlc"
"log"
_ "github.com/lib/pq"
)
func main() {
conn, err := sql.Open("postgres", "postgresql://postgres:postgres@localhost:5432/qiita?sslmode=disable")
if err != nil {
log.Fatal("DBに接続できません。", err)
}
arg := db.CreateUserParams{
UserName: "test",
Email: "test@example.com",
PhoneNumber: "123456789",
Password: sql.NullString{String: "password", Valid: true},
}
queries := db.New(conn)
insertResult, err := queries.CreateUser(context.Background(), arg)
if err != nil {
log.Fatal("登録に失敗しました。", err)
}
fmt.Println(insertResult)
selectResult, err := queries.GetUserByEmail(context.Background(), "test@example.com")
if err != nil {
log.Fatal("取得に失敗しました。", err)
}
fmt.Println(selectResult)
}
$ go run main.go
{1 test test@example.com 123456789 {password true}} // insertした内容
{1 test test@example.com 123456789 {password true}} // selectした内容
おわりに
sqlc を使用すると簡単に DB 操作のメソッドが作成できました!
今回は簡単な操作でしたが、他にも少し変則的な操作もやってみたので、今後まとめていきたいと思います。