【Go】ハードコードしているSQLをバイナリに埋め込んですっきりさせよう
はじめに
Goを書いていて、実行するSQLをハードコードしていることはないでしょうか
例えば、短いSQLだとこんなかんじ
age := 27
rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
if err != nil {
log.Fatal(err)
}
この例であれば、構文ミスがあっても実行前に気づくこともあるでしょう
ただ、次のように複数行にわたってSQLを書いてしまうこともあるのではないでしょうか
sqls := []string{
`UPDATE employees
SET salary = 5000;
`,
`UPDATE employees
SET salary = salary * 1.1
WHERE salary <= 10000;
`,
`UPDATE employees
SET salary = 5000
WHERE department = 'Sales';
`,
`UPDATE employees
SET salary = 5000
WHERE first_name = 'John' AND last_name = 'Doe';
`,
`UPDATE employees
SET salary = salary * 1.1
WHERE job_title = 'Manager' AND department = 'Sales';
`,
}
for _, sql :=range sqls {
_, err := tx.ExecContext(ctx, sql)
if err != nil {
log.Fatal(err)
}
}
O/Rマッパーをつかわない分、サッとSQLを用意して実行できるのは魅力的です
しかし、複数行にまたがるのでコードの可読性や、SQLの構文チェックがしにくいなどの懸念があると思います
SQLをファイルに分けよう
1つの解決策として、SQLをファイルに分けるという方法があるかと思います
そうすることで、コードの可読性は上がるでしょう
input, _ := os.ReadFile("input.sql")
// ファイルから読み込んだ複数のクエリを 1クエリずつ実行できるように処理
sqls := Something(input)
for _, sql := range sqls {
_, err := tx.ExecContext(ctx, sql)
if err != nil {
log.Fatal(err)
}
}
ただし、読み込んだファイルを有効なSQLにする処理は自分で実装する必要があります
SQLファイルをバイナリに埋め込もう
そこで、今回つくったものがこちらです
このライブラリは、SQLファイルを読み込み1クエリずつ実行できる形に変換します
以下サンプルです
package main
import (
"bytes"
"embed"
"fmt"
"github.com/uh-zz/sqload"
"github.com/uh-zz/sqload/driver/mysql"
)
//go:embed sql/*
var content embed.FS
func main() {
var (
buf bytes.Buffer // sql which read from file
sqls []string // sql after parse
)
loader := sqload.New(mysql.Dialector{}) // for PostgreSQL: postgresql.Dialector{}
if err := loader.Load(&content, &buf); err != nil {
fmt.Printf("Load error: %s", err.Error())
}
if err := loader.Parse(buf.String(), &sqls); err != nil {
fmt.Printf("Parse error: %s", err.Error())
}
fmt.Printf("%+v", sqls)
// [INSERT INTO table001 (name,age) VALUES ('alice', 10);]
}
go:embed
ディレクティブをつかってSQLファイルを実行バイナリに埋め込みます
こうすることで、プログラムからファイルを読み込むより効率的です(実行ファイルを配布するだけでよいです)
加えて、アピールポイントとしては、以下2つです
1. SQLファイルから読み込んだSQLが有効であるかをParseするときに検証します
Parserは以下を使用しています
MySQL
MySQL互換の分散DBであるTiDBのParserです
PostgreSQL
分散DBであるCockroachDBから分離されたPostgreSQL Parserです
2. 任意のSQLクライアントを使用できる
単に、読み込んだSQLを[]string
にするだけなので、SQLを実行するクライアントを任意に選べます
さいごに
現状、構文のサポートしているのは、MySQL, PostgreSQLのみになります
今後は、Parserを自前で実装したり、サポートするシステムを拡張できればと思います
IssueやPull Requestも歓迎ですので、どしどし送ってください!
もし気に入っていただけたらGitHubのスターとTwitterのフォローをよろしくおねがいします笑
Discussion