Go 言語で使える ORM と SQL ビルダー
先月あたりから余暇にコツコツ作ってた脆弱性情報の収集・管理ツール jvnman の最初のリリースを行った。
jvnman についての説明は上の記事を見ていただくとして,この記事ではツールの内部で行っている SQL 文のハンドリングについて。
jvnman は収集した脆弱性情報を SQLite データベース・ファイルに格納している。 jvnman には簡単な帳票出力機能も付いているが,メインは脆弱性情報の収集・蓄積である。 一度 SQLite データベース・ファイルを作っておけば作成したデータベースのハンドリングは他のツール(たとえば Office ツール)でもできる。
(ゆえに最初は軽く考えていて spiegel-im-spiegel/go-myjvn の
私は SQL 文を手書きで書くのは苦にならない性質なのだが(そもそも最初は A5:SQL Mk-2 みたいなツールで試して最適化を行うものだし),途中までコードを書いて流石に煩わしくなってきたので Go 言語製の ORM (Object-Relational Mapping) および SQL Builder パッケージを探すことにした。
というわけで,以下のパッケージを紹介。
- go-gorp/gorp: Go Relational Persistence - an ORM-ish library for Go
- Masterminds/squirrel: Fluent SQL generation for golang
go-gorp/gorp では Go 言語 の構造体(struct)を SQL のクエリ出力に関連付けることができる。 たとえば
type Vulnlist struct {
ID sql.NullString `db:"id,primarykey"`
Title sql.NullString `db:"title"`
Description sql.NullString `db:"description"`
URI sql.NullString `db:"uri"`
Creator sql.NullString `db:"creator"`
Impact sql.NullString `db:"impact"`
Solution sql.NullString `db:"solution"`
DatePublic sql.NullInt64 `db:"date_public"`
DatePublish sql.NullInt64 `db:"date_publish"`
DateUpdate sql.NullInt64 `db:"date_update"`
}
のように struct タグを使って関連付けるわけだ。
これで gorp.DbMap
インスタンス生成時に
db, err := sql.Open("sqlite3", "./jvndb.sqlite3")
if err != nil {
return err
}
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
dbmap.AddTableWithName(Vulnlist{}, "vulnlist")
としておけば
obj, err := dbmap.Get(Vulnlist{}, "JVNDB-2018-003082")
if err != nil {
return err
}
if obj == nil {
fmt.Println("mp data")
} else if ds, ok := obj.(*Vulnlist); ok {
if ds.Title.Valid {
fmt.Println("Title =", ds.Title.String)
} else {
fmt.Println("Title is NULL")
}
}
のように書くことができる。 INSERT や UPDATE についても同じようにできる。
Masterminds/squirrel は簡易 SQL Builder で,あまりガチガチの抽象化をしないのが気に入っている。 たとえば
var ds struct {
dateUpdate int64 `db:"date_update"`
}
if psql, args, err := squirrel.Select("date_update").From("vulnlist").Where(squirrel.Eq{"id": "JVNDB-2018-003082"}).ToSql(); err != nil {
return err
} else if err := dbmap.SelectOne(&ds, psql, args...); err != nil {
return err
} else {
fmt.Println(time.Unix(ds.dateUpdate, 0))
}
のように prepared statement とパラメータをちゃんと分離してくれる。
注意しないといけないのは From()
メソッドや Where()
メソッドなどで連結するたびにインスタンスのコピーが発生している点だろう1。
jvnman のようなツールなら気にするまでもないが,短時間に大量のトランザクションが発生する可能性がある場合は注意したほうがいいかもしれない。
ブックマーク
-
Base: how to connect to an SQLite database? [closed] - Ask LibreOffice
-
spiegel-im-spiegel/go-myjvn: Handling MyJVN RESTful API by Golang
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。
-
Value object として見るなら正しい動きなんだけどね。 Builder ツールとしてその動きはアリなんだろうか,とつい考えてしまう。 ↩︎