GoでSQLファイルを簡単に実行するためのライブラリを作ったので,その紹介をします.
sqlfileの簡単な使い方
詳しい使い方については,本記事の下,もしくは,上記のGitHubをご参照ください.
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ファイルを保持しておく必要があり,管理が面倒でした(以下,例).
.
└── 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ファイルを準備
注意! : 各クエリの最後に,必ず;
をつけてください.
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されます.
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ヶ月とかなので,もし間違っている箇所や改善案などがあれば,ぜひコメントお願いします.