sqlx/reflectxを触ってて、他のRDBMS関係のライブラリのcreate table部分の実装が気になったので調べてみたメモ
これは自分用のメモです。
goでRDBMSを触るときに、sqxは機能が小さくて良いのですが、手元で実行例的なコードを書くときに、SQL側のtable定義とgo側のstructの定義を書いたりするのが面倒になることがあります。実運用はともかくとして、ちょっとした実行例を試すときにstructを定義したらおしまいと言う世界観もそう悪くはありません。
加えて、ElasticsearchやAWSのAthenaやBigQueryなど、新しいデータストアを追加で利用したくなった場合にschemaの定義が要求されることはしばしばあります。その種の定義をgoのstructの定義だけでおしまいにできるという利便性が欲しくなることもありそうでした。
sqlx/reflectx
実は、sqlxの中にreflectxというパッケージが存在していたりします。これはちょっとしたstruct定義とtag定義を見るのに地味に便利な機能を持っていたりします。reflect.Typeを渡してあげるといい感じにタグの情報や型の値などを取り出してくれて便利です。
package main import ( "fmt" "reflect" "github.com/jmoiron/sqlx/reflectx" ) type Person struct { Name string `db:"name,unique"` Age int64 `db:"age,default=20"` } func main() { mapper := reflectx.NewMapper("db") tmap := mapper.TypeMap(reflect.TypeOf(Person{})) for i, info := range tmap.Index { fmt.Printf("%d\t%s\ttags=%+#v\n", i, info.Name, info.Options) } }
このコードを実行すると以下のような出力になります。
$ go run 00reflectx/main.go 0 name tags=map[string]string{"unique":""} 1 age tags=map[string]string{"default":"20"}
このreflectxを使ってお遊びでcreate tableやinsertを手軽に書けるようにするコードを書いてみていました。
ここまでが前置きです。そんなことをやっているうちに他のRDBMS用のライブラリの実装がどうなっているかどうか気になったので調べてみました。
対象のライブラリ
ここからが本題。今までの内容はすべて前置きです。
調べてみたライブラリは以下の4つです。独断と偏見でその場で思いついたものの実装を見てみたという感じです。
- https://github.com/go-gorp/gorp
- https://github.com/facebook/ent
- https://gitea.com/xorm/xorm (https://github.com/go-xorm/xorm)
- https://github.com/go-gorm/gorm
gorp
- CreateTablesIfNotExists() からスタート https://github.com/go-gorp/gorp/blob/master/db.go#L432
- https://github.com/go-gorp/gorp/blob/master/table.go#L173
- 差分はdialetに逃がす https://github.com/go-gorp/gorp/blob/master/dialect.go#L14
- 差分の実装例 https://github.com/go-gorp/gorp/blob/master/dialect_postgres.go#L14
結構素直に愚直に実装されていた。
ent
- schemaのCreatorを実装するらしい https://github.com/ent/ent/blob/master/dialect/sql/schema/migrate.go#L78
- 実態としてはMigrateのstructのcreate()が呼ばれる https://github.com/ent/ent/blob/master/dialect/sql/schema/migrate.go#L154
- init,types,txCreate()してからcommitが呼ばれる
- visitorと言う程でもないけれど、差分はsqlDialectというinterfaceで吸収 https://github.com/ent/ent/blob/master/dialect/sql/schema/migrate.go#L637
- 実装例とテスト https://github.com/ent/ent/blob/master/dialect/sql/schema/postgres.go https://github.com/ent/ent/blob/master/dialect/sql/schema/postgres_test.go
もう一段回キレイにwrapしている感ある(複雑)。gremlinなどにも対応しているせいかもしれない。
gorm
- Migratorというinterfaceがある https://github.com/go-gorm/gorm/blob/master/migrator.go#L33
- 分岐は既に終わってsturctにデータが入っている前提 https://github.com/go-gorm/gorm/blob/master/migrator/migrator.go#L155
- 方言部分の吸収は別リポジトリ。実装例。と言ってもbaseのやつを実行したあとのコメント程度のようだ https://github.com/go-gorm/postgres/blob/master/migrator.go
xorm
- Engine.DumpTables()のときにSQLが出力される https://gitea.com/xorm/xorm/src/branch/master/engine.go#L1034
- DialectのCreateTableSQLで文字列を生成している https://gitea.com/xorm/xorm/src/branch/master/engine.go#L562
- Dialectはこれ https://gitea.com/xorm/xorm/src/branch/master/dialects/dialect.go#L42
- 実装例はこの辺、愚直にそれぞれの方言ごとに実装している https://gitea.com/xorm/xorm/src/branch/master/dialects/postgres.go#L901 base部分はないわけではない https://gitea.com/xorm/xorm/src/branch/master/dialects/dialect.go#L76
- ただし謎がある(?)
- それとは別にEngine.CreateTables()があり、これはsession.createTable()を呼ぶ(?) https://gitea.com/xorm/xorm/src/branch/master/engine.go#L1034
はい。
ちょっとした感想
RDBMSだけを触ることに限って言えば、それぞれのライブラリが結構真面目に作り込んでいるので、わざわざ自前で実装し直す必要もないかなと思ったというのが正直な感想でした。
一方でかなりRDBMSに寄った実装になっている面もあったりするので、冒頭にあった他のデータストア用のschemaへの対応をと考えると、どれもover killな印象を持ちました。加えて、内部での利用に閉じてしまえば、複数の種類のデータベースに対応する必要はなかったり、利用する型の種類もわずかだったりしそうでもう少し手軽にやれるのかなと思ったりはしました。メモなのでこんな感じで突然おわり。
追記
reflectxはreflectベースの実装ですが、これがgo/typesを使った値を利用する静的解析ベースのものだったらどうだろう?というのは少し考えたりしていました。
デモ程度に動くものは手を動かせば作れそうですが、実は何を読み込ませるか指定するのが地味に面倒で何らかのマーカーが必要になったり引数として渡したりする必要があったり、付加的な情報をどう注入するかが悩ましかったりしそうです。
逆にreflectベースでもbuild tags経由でデフォルトではpackageの対象外にして、go generateでgo runを実行するみたいなことをやれば邪魔にならなかったりします(一方で、静的解析ベースの場合は事前にビルドして置けるのが便利だったりします。余談も余談になってしまうのでこのへんで中断)。