はじめに
Goでデータベースを扱う際、選択肢は主にdatabase/sqlによる直接記述やGormなどのORMがあります。しかし、database/sqlは文字列でのSQL記述が必要なため型安全性に課題があり、一方でORMは複雑なクエリの実装が困難です。そこで今回は、GoのSQLBuilderライブラリの中でも人気の高い「squirrel」を試してみました。
squirrelとは
squirrelは、Goで使用できるSQLBuilderライブラリです。主な特徴として以下が挙げられます。一般的なSQLBuilderならではのメリットを活用できます。
- SQLを型安全に構築できる
- メソッドチェーンで直感的に書ける
- WHERE句の条件を動的に組み立てやすい
基本的な使い方
SELECT文
func (ur *userRepository) GetByID(ctx context.Context, id int64) (*User, error) {
query := sq.
Select("id", "name", "email", "status", "created_at", "updated_at").
From("users").
Where(sq.Eq{"id": id})
queryString, queryArgs, err := query.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to build query: %w", err)
}
var user User
err = ur.db.QueryRowContext(ctx, queryString, queryArgs...).Scan(
&user.ID,
&user.Name,
&user.Email,
&user.Status,
&user.CreatedAt,
&user.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return &user, nil
}
INSERT文
func (ur *userRepository) Create(ctx context.Context, user *User) error {
user.CreatedAt = time.Now()
user.UpdatedAt = time.Now()
query := sq.
Insert("users").
Columns("name", "email", "status", "created_at", "updated_at").
Values(user.Name, user.Email, user.Status, user.CreatedAt, user.UpdatedAt).
Suffix("RETURNING id")
queryString, queryArgs, err := query.ToSql()
if err != nil {
return fmt.Errorf("failed to build query: %w", err)
}
err = ur.db.QueryRowContext(ctx, queryString, queryArgs...).Scan(&user.ID)
if err != nil {
return fmt.Errorf("failed to create user: %w", err)
}
return nil
}
UPDATE文
func (ur *userRepository) Update(ctx context.Context, user *User) error {
user.UpdatedAt = time.Now()
query := sq.
Update("users").
Set("name", user.Name).
Set("email", user.Email).
Set("status", user.Status).
Set("updated_at", user.UpdatedAt).
Where(sq.Eq{"id": user.ID})
queryString, queryArgs, err := query.ToSql()
if err != nil {
return fmt.Errorf("failed to build query: %w", err)
}
result, err := ur.db.ExecContext(ctx, queryString, queryArgs...)
if err != nil {
return fmt.Errorf("failed to update user: %w", err)
}
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get affected rows: %w", err)
}
if affected == 0 {
return fmt.Errorf("user not found: %d", user.ID)
}
return nil
}
DELETE文
func (ur *userRepository) Delete(ctx context.Context, id int64) error {
query := sq.
Delete("users").
Where(sq.Eq{"id": id})
queryString, queryArgs, err := query.ToSql()
if err != nil {
return fmt.Errorf("failed to build query: %w", err)
}
result, err := ur.db.ExecContext(ctx, queryString, queryArgs...)
if err != nil {
return fmt.Errorf("failed to delete user: %w", err)
}
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get affected rows: %w", err)
}
if affected == 0 {
return fmt.Errorf("user not found: %d", id)
}
return nil
}
動的なクエリ構築
squirrelの強みの一つは、条件に応じて動的にクエリを構築できる点です
func (ur *userRepository) Search(ctx context.Context, params SearchParams) ([]User, error) {
query := sq.
Select("id", "name", "email", "status", "created_at", "updated_at").
From("users")
// 検索条件を動的に追加
if params.Name != "" {
query = query.Where(sq.Like{"name": "%" + params.Name + "%"})
}
if params.Email != "" {
query = query.Where(sq.Eq{"email": params.Email})
}
if params.Status != "" {
query = query.Where(sq.Eq{"status": params.Status})
}
// 日付範囲検索
if !params.CreatedAfter.IsZero() {
query = query.Where(sq.GtOrEq{"created_at": params.CreatedAfter})
}
if !params.CreatedBefore.IsZero() {
query = query.Where(sq.LtOrEq{"created_at": params.CreatedBefore})
}
// ソート条件の追加
if params.OrderBy != "" {
if params.OrderDesc {
query = query.OrderBy(params.OrderBy + " DESC")
} else {
query = query.OrderBy(params.OrderBy + " ASC")
}
}
// ページネーション
if params.Limit > 0 {
query = query.Limit(uint64(params.Limit))
}
if params.Offset > 0 {
query = query.Offset(uint64(params.Offset))
}
queryString, queryArgs, err := query.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to build query: %w", err)
}
// 以降は省略
rows, err := ur.db.QueryContext(ctx, queryString, queryArgs...)
// ...
}
まとめ
squirrelを使用してみると、database/sqlと比較して型安全なSQLの構築が可能で、非常に良かったです。Gormのような強力なORMと比べるとコードは冗長になりますが、ユースケースに応じて使い分けることでメリットを最大限に活かせそうです!
参考