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

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

https://github.com/tanimutomo/sqlfile

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224401/a8e4399f-cec5-0512-2c5b-75ebaa9ea029.png)


## sqlfileの簡単な使い方
詳しい使い方については，本記事の下，もしくは，上記のGitHubをご参照ください．

```go: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](https://github.com/tanimutomo/sqlfile)

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

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

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

```shell: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ファイルを読み込んで，実行することです．

```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](https://github.com/tanimutomo/sqlfile)
長くなってしまいましたが，ここからが作成した sqlfile というライブラリの紹介になります．

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

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

[GitHub](https://github.com/tanimutomo/sqlfile)の方に，Usageを載せてありますが，ここでも簡単に説明をします．

### Installation
```
go get github.com/tanimutomo/sqlfile
```

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

```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されます．

```go: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ヶ月とかなので，もし間違っている箇所や改善案などがあれば，ぜひコメントお願いします．