1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZOZOAdvent Calendar 2024

Day 22

GoのSQLBuilder「squirrel」を使ってみた

Posted at

はじめに

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と比べるとコードは冗長になりますが、ユースケースに応じて使い分けることでメリットを最大限に活かせそうです!

参考

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?