アプリケーションを作る上でデータベースを避けて通ることはできません。その中で最もポピュラーでパワフル(かつ普及している)のはSQLでしょう。
golangでも快適にSQLを操作したい。
このエントリでやること
golangでSQLを操作する方法を、「基本(databse/sql)」から、SQLクエリを自動生成する「クエリジェネレータ」、最後に「ORM」という順序で解説します。
ライブラリの特徴などをいくつか抑えていくだけであって、網羅的ではない。雰囲気だけ。
操作方法
基本(database/sql)
標準ライブラリだけを使って頑張る。基本的にSQLを手書きして、変数とのマッピングも手で当てることになる。
sql.(*DB).Exec()を使うと、SQLクエリをそのまま叩ける。プリペアドステートメントはクエリのあとにガシガシ置いていくカンジ。
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
return err
}
_, err = db.Exec(
`CREATE TABLE "HOGE" ("id" INTEGER, "hoge" VARCHAR(255))`,
)
if err != nil {
return err
}
_, err = db.Exec(
`INSERT INTO "HOGE" ("id", "hoge") VALUES (?, ?) `,
1,
"hogege",
)
if err != nil {
return err
}
SELECTでentityを取りに行く時は、sql.(*DB).Query()を使う。こいつは列ごとにデータを拾ってきてくれるので、Scan()でマッピングする。
rows, err := db.Query(
`SELECT * FROM "HOGE"`,
)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
id, hoge := 0, ""
err = rows.Scan(&id, &hoge)
if err != nil {
return err
}
fmt.Println(id, hoge)
}
リレーションの解決やSQLのテーブルとgoのstruct(モデル?)との紐付けは、この中でガシガシと書いていく。非常に厳しいが、イロイロ悩まなくて済むのでまぁアリッちゃアリかもしれない。規模が小さいか、極端に大きいなら。
クエリビルダ
SQLをまるっと手で書くのは厳しいので、クエリビルダを使うという手がある。クエリビルダはORMのようにデータとオブジェクトを紐付けてくれるものではないが、SQLクエリそのものを作ることを手助けしてくれる。
プレーンテキストなSQLはSQLとしてvalidであることを保証できないが、クエリビルダを使えばある程度それができるのが良いし、コードとしての見栄えも良い。(見栄えは好みかな?) 反面、クエリビルダはあらゆるSQLを吐き出すことが出来るわけではないので、自由度は若干下がる。
クエリビルダとしては、次の2つの実装が有名だとおもう。
- squirrel
- https://github.com/lann/squirrel
- godropbox/database/sqlbuilder
- https://github.com/dropbox/godropbox/tree/master/database/sqlbuilder
sqlbuilderはDropboxが作ったクエリビルダで、かなりシッカリとした造りになっている。テーブルの定義からSQLクエリを作る形になっているので、テーブルに存在しないカラムなんかも検出できる。
import sb "github.com/dropbox/godropbox/database/sqlbuilder"
t := sb.NewTable(
"HOGE",
sb.IntColumn("id", false),
sb.StrColumn(
"hoge",
sb.UTF8,
sb.UTF8CaseSensitive,
false,
),
)
query, err := t.Select(t.C("id"), t.C("hoge")).Where(sb.EqL(t.C("id"), 0)).String("test")
if err != nil {
return err
}
// query == "SELECT `HOGE`.`id`,`HOGE`.`hoge` FROM `test`.`HOGE` WHERE `HOGE`.`id`=0"
// err == nil
rows, err := db.Query(query)
// 〜〜以下略〜〜
吐出されたSQLをdatabase/sqlなりなんなりで実行すれば良い。ちなみに、godropboxのsqlbuilderはMySQLにしか対応していないのが残念ポイント。
# ちなみに、プリペアドステートメントを使ってくれないのもすごく不安になる。一応エスケープはするんだけれど、これで充分なことを信用して良いものか。
それに対して、squirrelはSQLの構文をそのままgoの構文に置き換えるようなシンプルなAPIになっている。使いやすいが、カラム名が正しいかなどはコーダの集中力以上の保証が無い。
query, attrs, err := sq.Select("*").From("HOGE").Where(sq.Eq{
"id": 1,
}).ToSql()
if err != nil {
return err
}
// query == "SELECT * FROM HOGE WHERE id = ?"
// attrs == []interface{}{1}
// err == nil
rows, err := db.Query(query, attrs...)
// 〜〜以下略〜〜
ちなみに、squirrelには作ったSQLをそのまま吐き出すだけでなく、直接DBを叩いて結果を返してくれる使い方もある。地味に嬉しい便利機能。
rows, err := sq.Select("*").From("HOGE").Where(sq.Eq{
"id": 1,
}).RunWith(db).Query()
squirrelは対応している構文に制限があったりして少し辛いのだけれど、気軽に使えるしソコソコ気にいっている。
ORM
まあ、データベース触るならORM使いたい。生SQLを書くのは個人的には3K業務(キツイ、キビシイ、カエリタイ)といえる。
gorpはORMとは名乗っていない("Go Relational Persistence"としている)し、実際にモデル間のリレーションには関知しないが、マァわりとORMに近い何かである。うーん、SQLのテーブルと、goのstructの間を繋ぐマッパーというのが正確か。
import "github.com/coopernurse/gorp"
type Hoge struct {
Id int `db:"id"`
Hoge string `db:"string"`
}
m := new(Hoge)
_, err := dbmap.Get(m, 1)
if err != nil {
return err
}
// m == &Hoge{Id: 1, Hoge: "hogestring"}
// err == nil
モデルを定義しておけばクエリの結果を自動的に充ててくれるので、sqlパッケージを使うよりも楽。
また、クエリの実行後/実行前に関数を実行させることもできるので、これにリレーションの面倒を見させれば、少しORMに近づくことができる。
type Hoge struct {
Id int `db:"id"`
Hoge string `db:"string"`
}
type Piyo struct {
Id int `db:"id"`
HogeId int `db:"hoge_id"`
// relation
Hoge *Hoge `db:"-"`
}
func (m *Piyo) PostGet(s gorp.SqlExecutor) error {
hoge := new(Hoge)
_, err := s.Get(hoge, m.HogeId)
if err != nil {
return nil
}
m.Hoge = hoge
return nil
}
m := new(Piyo)
_, err := dbmap.Get(m, 1)
if err != nil {
return err
}
// m == &Piyo{Id: 1, HogeId: 1, Hoge: &Hoge{Id: 1, Hoge: "hogestring"}}
// err == nil
PostGet()はSELECTが終わったあとに呼ばれるインジェクション的な関数で、この中で関係しているオブジェクトを呼んでくると、リレーションが解決されたORM的な結果が得られる。
残念なのは、SQLのjoinとかを使えないので、パフォーマンスが最悪。シンプルだけど実用に厳しい面がある。
gorpは人気があり、そこそこ使われているようだ。ちなみに、似たコンセプトのORMに"genmai"というのもあって、こちらも便利機能がイロイロあって良い。また、"xorm"というキャッシュ機能付きの同様なライブラリもある。goのORMは"ORM"というよりは"Table-structマッパー"みたいなカンジのが多い。
それらに対してもっと筋肉質なORM、というかORMらしいORMとしてgormというのもある。gormはまさにORMで、リレーションの面倒も見てくれる。かなり強引なマイグレーション機能などの便利っぽい機能もイロイロ持っている。
type Piyo struct {
Id int
Hoge []Hoge `gorm:many2many:hoge_id`
}
struct tagで"many2many"と指示してやれば、多対多のリレーションとして認識してくれて、オブジェクトの取得時に適当に拾ってきてくれる。このようにgormはオブジェクト間のリレーションをきちんと解釈してくれるので、golangで"ORM"と言われるものの中では最もORMらしい動きをすると思う。
そういうわけでgormは便利そうなんだけれど、個人的には魔法っぽい機能が多くて安心できていない(コード追いかけるのが辛い)。
まとめ
golangでSQLデータベースを操作する方法として、"database/sql"のオーバービュからクエリビルダ、ORMまでよく使われているあたりをおさらいしてみた。
最近はgolangでデータベースを弄るための選択肢も、そこそこ充実してきたと思う。
ちなみに、僕個人のはなしをすると、(特段の事情が無い限りは)squirrelで作ったクエリをgorpでstructにマッピングさせている。制限はいろいろ有るんだけれど、まぁまぁ戦える。
雑感
でもgolangでSQLやるのは正直キビシイ。関係データベースとgolangの型構造の親和性がはっきり言って良くない。ORM達があんまりORMしていないのもそういう背景がある。
人生はキビシイ。