Go 言語で使える ORM と SQL ビルダー

先月あたりから余暇にコツコツ作ってた脆弱性情報の収集・管理ツール jvnman の最初のリリースを行った。

jvnman についての説明は上の記事を見ていただくとして,この記事ではツールの内部で行っている SQL 文のハンドリングについて。

jvnman は収集した脆弱性情報を SQLite データベース・ファイルに格納している。 jvnman には簡単な帳票出力機能も付いているが,メインは脆弱性情報の収集・蓄積である。 一度 SQLite データベース・ファイルを作っておけば作成したデータベースのハンドリングは他のツール(たとえば Office ツール)でもできる。

(ゆえに最初は軽く考えていて spiegel-im-spiegel/go-myjvnサンプル (おまけ) として jvnman を組み込む予定だった。思ったより依存パッケージが多いことが分かってリポジトリを別にしたんだけど)

私は SQL 文を手書きで書くのは苦にならない性質なのだが(そもそも最初は A5:SQL Mk-2 みたいなツールで試して最適化を行うものだし),途中までコードを書いて流石に煩わしくなってきたので Go 言語製の ORM (Object-Relational Mapping) および SQL Builder パッケージを探すことにした。

というわけで,以下のパッケージを紹介。

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() メソッドなどで連結するたびにインスタンスのコピーが発生している点だろう1jvnman のようなツールなら気にするまでもないが,短時間に大量のトランザクションが発生する可能性がある場合は注意したほうがいいかもしれない。

ブックマーク

参考図書

photo
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan Brian W. Kernighan 柴田 芳樹
丸善出版 2016-06-20
評価

スターティングGo言語 (CodeZine BOOKS) Go言語によるWebアプリケーション開発 Kotlinスタートブック -新しいAndroidプログラミング Docker実戦活用ガイド グッド・マス ギークのための数・論理・計算機科学

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。

reviewed by Spiegel on 2016-07-13 (powered by G-Tools)


  1. Value object として見るなら正しい動きなんだけどね。 Builder ツールとしてその動きはアリなんだろうか,とつい考えてしまう。 [return]