🦜
Go製CGOフリーなSQLiteドライバーでentを使う
CGOフリーなSQLiteドライバーmodernc.org/sqlite は
以下のC記述をGoにトランスレートする仕組みで作られたPure-GoなSQLite3ドライバーです。
これを利用して型安全なORM「ent」でつかう方法をまとめてみます。
ドライバーラッパー
entがsqliteドライバーに要求するForeignKeysフラグがmodernc.org/sqlite では標準でスイッチできないのでそこをサポートするコードを差し込みます。幸い、ドライバー名はsqlite3
ではなくsqlite
で重複していないのでsqlite3
で登録することでentがサポートするドライバー名に合わせることができます。
sqlite_driver.go
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"modernc.org/sqlite"
)
type sqliteDriver struct {
*sqlite.Driver
}
func (d sqliteDriver) Open(name string) (driver.Conn, error) {
conn, err := d.Driver.Open(name)
if err != nil {
return conn, err
}
c := conn.(interface {
Exec(stmt string, args []driver.Value) (driver.Result, error)
})
if _, err := c.Exec("PRAGMA foreign_keys = on;", nil); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to enable enable foreign keys: %w", err)
}
return conn, nil
}
func init() {
sql.Register("sqlite3", sqliteDriver{Driver: &sqlite.Driver{}})
}
上記のファイルをimportできれば、modernc.org/sqlite を github.com/mattn/go-sqlite3 の代わりにつかうことができるようになります。
あとはentのチュートリアル通り。
go install entgo.io/ent/cmd/ent@latest
mkdir sample
cd sample
go mod init sample
ent init User
生成されたコードent/schema/user.go
のFieldsメソッドにフィールド宣言を書き足します。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").Default("unknown"),
field.Int("age").Positive(),
}
}
上記のスキーマでコード生成を実行します。
go generate ent
以下のようなコードでマイグレーションとユーザーのINSERTを行えます。
package main
import (
"context"
"log"
"modernc-sqlite/ent"
"entgo.io/ent/dialect"
_ "modernc.org/sqlite"
)
func main() {
entOptions := []ent.Option{}
entOptions = append(entOptions, ent.Debug())
client, err := ent.Open(dialect.SQLite, "file:ent.sqlite?cache=shared", entOptions...)
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
ctx := context.Background()
u, err := client.User.
Create().
SetAge(32).
SetName("inoue").
Save(ctx)
if err != nil {
log.Fatalf("failed creating user: %v", err)
}
log.Println("user was created: ", u)
}
ベンチマーク
コード
func BenchmarkInsert(b *testing.B) {
ctx := context.Background()
for i := 0; i < b.N; i++ {
_, err := client.User.
Create().
SetAge(20 + i%32).
SetName(fmt.Sprintf("hoge%08d", i)).
Save(ctx)
if err != nil {
b.Log(err)
b.Fail()
}
}
}
mattn/go-sqlite3
2021/09/08 13:23:22 Do stuff BEFORE the tests!
goos: darwin
goarch: amd64
pkg: mattn-sqlite3
cpu: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
BenchmarkInsert-4 21843 53604 ns/op 3137 B/op 117 allocs/op
PASS
2021/09/08 13:23:24 Do stuff AFTER the tests!
ok mattn-sqlite3 1.978s
modernc.org/sqlite
2021/09/08 13:25:24 Do stuff BEFORE the tests!
goos: darwin
goarch: amd64
pkg: modernc-sqlite
cpu: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
BenchmarkInsert-4 14449 84427 ns/op 2363 B/op 77 allocs/op
PASS
2021/09/08 13:25:26 Do stuff AFTER the tests!
ok modernc-sqlite 2.426s
ER図の生成
以下のコマンドでer.htmlが出力されます。
go install github.com/a8m/enter@latest
enter ./ent/schema
er.htmlをブラウザで開くとER図がみれます!
まとめ
- entは型安全でORMが扱えるし、オーバーヘッドも少なめで良い
- CGO依存が残ることはGoの利点のいくつかを損なう
- CGO依存は複数の処理系の依存管理が必要になる
- 環境構築の手間もコンパイル時間も実行時メモリ使用量も悪化する
- クロスコンパイル環境も難解な作業を伴う
- 以上のデメリットから解放されることはとっても嬉しい
- 実行速度だけは若干落ちる(moderncの仕組み上、C実装のエミュレーションをする関係)
Discussion