目的
Goを使ってPostgreSQLにSQLファイルを投げる
単にSQLを実行したいのではなく、SQLファイルのSQLを実行したい
WebサーバのDB側構築にあたって、テーブル構造とか初期データを自動生成したかったのでGradleとかAntとかのSQLローダー的なやつを作ってみたかった
ぶっちゃけ普通にバッチでよくね?とか思うところはあるものの、とりあえずGoで作ってみる
環境
go version go1.12.5 windows/amd64
PostgreSQL10.9
GoでのDB接続
GoでSQL実行等を行うためにはdatabase/sqlパッケージを利用する
https://golang.org/pkg/database/sql/
またデータベースに接続するためのドライバが必要
各DBへのドライバは下記を参照
https://github.com/golang/go/wiki/SQLDrivers
今回はPostgreSQLへの接続ドライバとして、Go純正ドライバである
https://github.com/lib/pq を利用する
ドライバのドキュメントは下記を参照
https://godoc.org/github.com/lib/pq
ドライバのインストールはgo getするだけ
go get github.com/lib/pq
database/sqlパッケージのOpen関数により、DBへの接続情報を管理するDB構造体を取得できる
ドライバは_ "github.com/lib/pq"でインポートすれば、Open時のドライバ指定に"postgres"の指定で利用できる模様
下記はドキュメントのサンプル
import (
"database/sql"
_ "github.com/lib/pq"
)
func main() {
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
age := 21
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
}
Open関数に引き渡す接続文字列に、起動しているPostgreSQLの設定を渡してやると簡単につながる
SQLファイルの実行
今回下記のような関数を試しに作ってみてTest関数から実行してみた
簡単な動作試験用の関数なので実装が汚くても一旦放置する
// Loader DBに接続してそのままリテラルのSQLを実行する
func Loader() error {
// DBの接続文字列を返すための簡単な構造体
// GetConnStr()で接続文字列を取得できる
setting := DBSetting{
ConstHost,
ConstPort,
ConstUserAdmin,
ConstPasswordAdmin,
ConstSslmode,
}
// DBのOpenとClose
db, err := sql.Open("postgres", setting.GetConnStr())
defer db.Close()
if err != nil {
return err
}
fmt.Println("db access success")
// create userで新しいユーザを作成する
// 実際のSQL実行時はPingやPingContextによる接続確認を挟むべきだが今回は割愛
// https://golang.org/pkg/database/sql/#DB.Ping
result, err := db.Exec("create user posttest with password 'posttest' createdb")
if err != nil {
return err
}
fmt.Println(result.RowsAffected())
return nil
}
上記を動かした結果、posttestユーザがDBに作成されたことを確認できた
おー簡単だなー
しかし、現状だとソースコードにべた書きのSQLを実行しているだけであり、SQL文はSQLファイルで管理したいのでこれを改善したい
試しに同ディレクトリに下記のようなSQLファイルを作ってみた(中身は上のべた書きSQLと同じ)
create user posttest2
with
password 'posttest2'
createdb
このSQLファイルを読み込むためにGoではいろいろと方法がありそうだが、今回はio/ioutilパッケージのReadFile関数を使う
https://golang.org/pkg/io/ioutil/#ReadFile
func main() {
content, err := ioutil.ReadFile("testdata/hello")
if err != nil {
log.Fatal(err)
}
fmt.Printf("File contents: %s", content)
}
ReadFile関数は引数としてファイルパスを受け取り、読み込みに成功した場合にbyte配列を返す
このbyte配列をbytesパッケージのNewBuffer関数でBuffer構造体に変換し、BufferのString関数で文字列に直すことでSQLファイルをStringとして取得できるので、そのままSQL実行の関数に突っ込んでやればSQLファイルの実行ができるんじゃね?というのが今回の実装である
試しに作ってみたSQLファイルを読み込む関数が下記
// ReadSQLFile 引数で受け取ったファイルを読み込み、文字列にして返す
func ReadSQLFile(path string) (string, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
b := bytes.NewBuffer(content)
return b.String(), nil
}
上記を適当なTest関数で読んでみたところうまくSQLファイルの文字列が返ってきたので、先ほどのSQL実行用の関数から呼んでみて、ユーザが作成されるか確認してみる
// ファイルを読み込んで実行
//result, err := db.Exec("create user posttest with password 'posttest' createdb")
sql, err := ReadSQLFile("create_user.sql")
if err != nil {
return err
}
result, err := db.Exec(sql)
ちゃんとposttest2ユーザが作成されている!
ということでSQLファイルを読み込んで実行も普通にできそうな感じでした
sqlパッケージのStmtあたりの利用もSQLファイルでSQL文を管理できそう
終わりに
実際のローダーは複数の(それなりの数の)SQLファイルを投げることになるのでもうちょっと複雑だとは思うが、SQLファイルを読み込んで実行さえできてしまえばあとはどうとでもなりそうなので今回はそこまでは触れない
この方法でもう少し拡張してみてエラー等でつまらないかは今後確認していく