🦎
csvデータをdbにInsertするCLIツールcsqlの紹介
はじめに
もうすぐクリスマスですね。今年のクリスマスは家でkubernetesと戦っていると思います。ところで昨今はクリーンアーキテクチャの流行りなども相まって、UnitTestやE2Eテストなど多くのテストがクリスマスもgithub actionsを走っていると思います。特にDBに関連したテストは重要度が高いですよね。というのも相まって、csvにあるデータをDBに移行できたらテスト用の大量データとかも楽そうなだあと思って作りました。
csql
リポジトリはこれです。starください。
Install
go install github.com/seipan/csql
Usage
現在はmysql,postgres,sqlite3,mariadb(mysqlやん)に対応しています。cliのoptionとしては
Usage:
csql [flags]
Flags:
-c, --check check csv format
-d, --dsn string DSN for Connecting Database
-h, --help help for csql
-p, --path string FilePath for Parsing CSVFile
-q, --query output query
-t, --type string Database Type
以上のようなものがあります。
csqlにはいろいろなオプションがあります。まずは、checkオプションです。
--check
option
if success patern
csql --check --path=./testdata/csv/test01.csv --type=mysql --dsn=hogehoge
___________ ____ __
/ ____/ ___// __ \ / /
/ / \__ \/ / / / / /
/ /___ ___/ / /_/ / / /___
\____//____/\___\_\/_____/
csv format is correct
failed pattern
csql --check --path=./testdata/csv/test02.csv --type=mysql --dsn=hogehoge
___________ ____ __
/ ____/ ___// __ \ / /
/ / \__ \/ / / / / /
/ /___ ___/ / /_/ / / /___
\____//____/\___\_\/_____/
csv format is incorrect : table name is empty
exit status 1
check optionではcsvがcsqlのフォーマットに沿っているかどうかチェックします。
例えば今回failed patternのtest02.csvは以下のようなフォーマットです。
,name,id,email
,tarou,12,hoge@example.com
,hanako,13,huga@example.com
このフォーマットだとテーブル名が足りないってエラーが帰ってきます。
--query
option
csql --query --path=./testdata/csv/test01.csv --type=mysql --dsn="hoge:hoge@tcp(hoge:3306)/hoge?charset=utf8&parseTime=true"
___________ ____ __
/ ____/ ___// __ \ / /
/ / \__ \/ / / / / /
/ /___ ___/ / /_/ / / /___
\____//____/\___\_\/_____/
INSERT INTO user (name, id, email) VALUES (?, ?, ?)
queryオプションは、csvをinsertする際にどのようなqueryが走るかを返してくれます。
Insert
最後に実際にInsertします。
csql --path=./testdata/csv/test01.csv --type=mysql --dsn="hoge:hoge@tcp(localhost:3308)/hoge?parseTime=true&collation=utf8mb4_bin"
___________ ____ __
/ ____/ ___// __ \ / /
/ / \__ \/ / / / / /
/ /___ ___/ / /_/ / / /___
\____//____/\___\_\/_____/
insert 2 rows
Inserting: | 100%%
こんな感じでinsertします。
実装
ちょこっとだけ内部実装についてもお話します。今回mysql,sqlite3,postgresでパターン分けするために、Interfaceを切って場合分けてます。
type Inserter interface {
Query() string
Insert() error
}
これをmysqlの実装だと
package mysql
import (
"database/sql"
"fmt"
"strings"
"github.com/seipan/csql/query"
)
type MySQLInserter struct {
keys query.KeyValues
tableName string
db *sql.DB
}
func (i *MySQLInserter) Query() string {
placeholders := make([]string, 0, len(i.keys))
keys := make([]string, 0, len(i.keys))
for _, kv := range i.keys {
keys = append(keys, kv.Key)
placeholders = append(placeholders, "?")
}
query := fmt.Sprintf(
"INSERT INTO %s(%s) VALUES (%s);",
i.tableName,
strings.Join(keys, ", "),
strings.Join(placeholders, ", "),
)
return query
}
func (i *MySQLInserter) Insert() error {
if i.db.Ping() != nil {
return fmt.Errorf("failed to connect to database: %w", i.db.Ping())
}
stmt, err := i.db.Prepare(i.Query())
if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err)
}
defer stmt.Close()
values := make([]interface{}, 0, len(i.keys))
for _, kv := range i.keys {
values = append(values, kv.Value)
}
_, err = stmt.Exec(values...)
if err != nil {
return fmt.Errorf("failed to execute statement: %w", err)
}
return nil
}
func NewMySQLInserter(kv query.KeyValues, tableName string, db *sql.DB) query.Inserter {
return &MySQLInserter{
keys: kv,
tableName: tableName,
db: db,
}
}
こんな感じですね。
終わりに
今回は作ったCLIにツールについて書きました。読んでくれてありがとうございます。あとはパフォーマンス改善など頑張りたいです。
Discussion