LoginSignup
11
3

More than 3 years have passed since last update.

sqlfile : GoでSQLファイルを簡単に実行する

Last updated at Posted at 2020-09-12

GoでSQLファイルを簡単に実行するためのライブラリを作ったので,その紹介をします.

image.png

sqlfileの簡単な使い方

詳しい使い方については,本記事の下,もしくは,上記のGitHubをご参照ください.

main.go
import (
  "database/sql"
  "github.com/tanimutomo/sqlfile"
)

db, err := sql.Open("DBMS", "CONNECTION")

s := sqlfile.New()
err := s.File("example.sql")
res, err := s.Exec(db)

はじめに

Goで複数のクエリが書かれたSQLファイルを実行できるライブラリがなかったので,作りました. sqlfile

作成の背景
Goで,DAO (data access object) パッケージのユニットテストの際にseed data挿入が必要になりました.
RailsのFixtureのような,seed dataを作成するためのパッケージとして,testfixtures という便利なライブラリがあります.(RailsのFixturesほどの高価な機能はないです.)
しかし,以下のような点で使い勝手が悪かったです.
(もしかしたら,testfixturesの設定で解決できるのかもしれませんが,見つけられませんでした)

  • テーブルごとにファイルを作成する必要がある
  • テストごとにレコードが削除されるわけではないため,レコードを削除したいテーブルは,table_name.ymlというファイルを作成し,中身は[]を書いておく必要がある
  • 空のymlファイルがあるとエラーになる

これらのことから,
DBのデータに変更を加える関数をテストする際に,テストケースごとにseed dataを用意しようとすると,testdata/に大量のyamlファイルを保持しておく必要があり,管理が面倒でした(以下,例).

tree
.
└── testdata/
    ├── func1/
    │   ├── success_login_user/
    │   │   ├── users.yml
    │   │   ├── articles.yml
    │   │   ├── tag.yml
    │   │   └── article_tags.yml
    │   ├── fail_not_found_user/
    │   │   ├── users.yml
    │   │   ├── articles.yml
    │   │   ├── tag.yml
    │   │   └── article_tags.yml
    │   └── ...
    ├── func2/
    │   └── ...
    └── ...

特に,空のファイルが許容されてないので,

  • 不要になったテーブルは,ファイルごと削除する必要がある
  • seed data間の関係をみる際に,ファイルを跨ぐ必要がある.

以上の理由と,これらの問題に関して,testfixturesの挙動を調べるのが面倒だったので,SQLファイルを直接書いて実行したくなりました.

SQLファイルを簡単に実行できる方法がない

ということで,testfixturesを諦めて,SQLファイルを書こうと思ったわけですが,
ここで,GoにはSQLファイルを簡単に実行できる方法がないことに気づきました.

ここでやりたいのは,以下のような複数のクエリが書かれたSQLファイルを読み込んで,実行することです.

DELETE FROM users; -- delete all records from users table

INSERT INTO users ( -- new user
  id, name, created_at, updated_at
) VALUES (
  1, 'foo', NOW(), NOW()
);

単一のSQLのクエリであれば, database/sql を使えばできますが,一度に複数のクエリを一度に実行できません.
また,SQLファイルを1行ずつ読み込んで,database/sqlで,実行することも可能ですが,以下のような問題に対処する必要があります.

  • 一つのクエリが,複数行にまたがっている
  • コメントアウト部分を取り除く

そこで,これらの問題に対処して,適切にクエリを読み込み,実行するためのライブラリを作りました.

sqlfile

長くなってしまいましたが,ここからが作成した sqlfile というライブラリの紹介になります.

本記事を執筆した時点の最新版v1.0.0では,ざっくり以下の2機能しかありません.

  • SQLファイルの読み込み
  • 読み込んだクエリの実行

GitHubの方に,Usageを載せてありますが,ここでも簡単に説明をします.

Installation

go get github.com/tanimutomo/sqlfile

Usage

SQLファイルを準備
注意! : 各クエリの最後に,必ず;をつけてください.

example.sql
INSERT INTO users ( -- users table
  id, name, email, created_at, updated_at
) VALUES (
  1, 'user1', 'user1@example.com', now(), now() 
);

INSERT INTO articles ( -- articles table
  id, user_id, title, content, created_at, updated_at
) VALUES (
  1, 1, 'title1', "-- About -- \n I'm sqlfile.", now(), now() -- post1
), (
  2, 1, 'title2', '- About - \n I''m sqlfile.', now(), now() -- post2
);

SQLファイルの読み込みと,実行
File で読み込んで, Execで実行です.
Execの中では,内部でトランザクションを発行しているため,どこかのクエリでエラーが起こったら,全てがRollbackされます.

main.go
import (
  "database/sql"
  "github.com/tanimutomo/sqlfile"
)

// Get a database handler
db, err := sql.Open("DBMS", "CONNECTION")

// Initialize SqlFile
s := sqlfile.New()

// Load input file and store queries written in the file
err := s.File("example.sql")

// Load input files and store queries written in the files
err := s.Files("example.sql", "example2.sql")

// Load files in the input directory and store queries written in the files
err := s.Directory("./examples")

// Execute the stored queries
// transaction is used to execute queries in Exec()
res, err := s.Exec(db)

まとめ

Goでdaoのwriterオブジェクトをテストする際のベストプラクティスに関しては,模索中です.
開発中に,SQLファイルを実行したいタイミングがあれば,是非使ってみてください.
Goはまだ書き始めて4ヶ月とかなので,もし間違っている箇所や改善案などがあれば,ぜひコメントお願いします.

11
3
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
11
3