8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

sqlcを使ってデータ作成と取得を行う方法

Posted at

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 を記述します

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)プリペアードステートメントを作成します。

users.sql
-- 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

生成されたコード

db.go
// 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,
	}
}
models.go
// 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"`
}
user.sql.go
// 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 操作のメソッドが作成できました!
今回は簡単な操作でしたが、他にも少し変則的な操作もやってみたので、今後まとめていきたいと思います。

参考

8
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?