2 minutes
[GCP] CloudSQLのデータをCSV出力するには
お世話になっております。
しゃまとんです。
GCPのマネージドデータベースであるCloudSQLにて、DBからCSV化を行いたいと思いもろもろを調べていたのですが、 ちょっと手こずったのでメモしておきたいと思います。
CSV出力はどのようにするかぐぐってみると、よく出てくるのが
SELECT ... INTO OUTFILE
でのやり方です。
ですが、これをCloudSQLに対して実行すると
Access denied for user user@cloudsqlproxy~% (using password: YES)
ようなエラーとなり拒否されてしまいます。
これは公式にもあるのですが、CloudSQLではOUTFILEがサポートされていません。
やり方として、CloudStorageに対してエクスポートする手法を公式で説明していますが いちいち経由するのがちと煩わしいなというところで、別の対処法でCSV化することができます。
やり方は結構シンプルでシェル上でデータを取得(SELECT)して、出力された文字を置換していくだけです。
mysql -h localhost --protocol TCP -P 3306 \
-uuser -ppassword \
-e "select * from user;" | sed -e 's/^/"/g' | sed -e 's/$/"/g' | sed -e 's/\t/","/g' > ./output.csv
上記の例では、全ての項目に対して「"」ではさみ、カンマで区切ったものになります。
"id","name"
"1","taro"
"2","jiro"
これでシェル上では対応できるのですが、自分はスクリプト化したかったのでGoで同じような処理をするものを書いてみました。
func db2csv(user, pass, host, port, dataBase, tableName string) {
csvFileName := tableName + ".csv"
dataSQL := "SELECT * FROM " + tableName
ret := execCommand(
"mysql", "-h", host, "--protocol", "TCP", "-P", port, "-u"+user, "-p"+pass, dataBase, "-e", dataSQL,
)
scanner := bufio.NewScanner(strings.NewReader(string(ret)))
var columns []string
isHeaderChecked := false
for scanner.Scan() {
str := ""
if isHeaderChecked {
str = "\"" + scanner.Text() + "\""
str = strings.Replace(str, "\t", "\",\"", -1)
} else {
str = strings.Replace(scanner.Text(), "\t", ",", -1)
isHeaderChecked = true
}
columns = append(columns, str)
}
columnStr := strings.Join(columns, "\n")
err := ioutil.WriteFile(csvFileName, []byte(columnStr), 0644)
if err != nil {
panic(err)
}
}
func execCommand(command string, args ...string) []byte {
path, err := exec.LookPath(command)
if err != nil {
panic(err)
}
cmd := exec.Command(path, args...)
var stdErr, stdOut bytes.Buffer
cmd.Stderr = &stdErr
cmd.Stdout = &stdOut
// exec
err = cmd.Run()
if err != nil {
fmt.Println(stdErr.String())
panic(err)
}
return stdOut.Bytes()
}
シェルスクリプトと若干違うのですが、最初の行のカラムには「"」をつけないようにしています。 CSVからINSERT文を生成するさいに邪魔になっていたので。
id,name
"1","taro"
"2","jiro"
最初は、シェルの処理をそのままGo内で使おうとしていたのですが、なかなかうまくいかなかった(パイプが結構めんどい)ので、 他の機能を使って同じような処理を行いました。
ちなみに、メモリコピーが走ったりするので、簡易的なスクリプト等にとどめていただけると幸いです。
以上です。
■ 参考