DBから直接golangのモデルを生成するxoのご紹介
Webアプリを開発している時に、DBのモデル定義の方法にはいろいろなやり方があると思います。
xoは、DBから直接golangのモデル定義を自動生成するツールです。
- PostgreSQL
- MySQL
- Oracle
- Microsoft SQL Server
- SQLite
に対応しており、良く使われるRDBをほぼカバーしていると思います。
インストール
goのツールですので、go getでインストールできます。
$ go get -u golang.org/x/tools/cmd/goimports (依存性のため)
$ go get -u github.com/knq/xo
これでxoというコマンドがインストールされたと思います。
使い方
ではさっそく使ってみましょう。使用するDBはPostgreSQLです。
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name TEXT,
age INT NOT NULL,
weight INT,
created_at timestamptz NOT NULL,
updated_at timestamptz
);
CREATE INDEX users_name_idx ON users(name);
このようなテーブルとインデックスがあったとしましょう。
xoを実行します。
$ mkdir -p models # 事前にディレクトリを作っておく
$ xo pgsql://localhost/example -o models
そうすると、models以下に
- user.xo.go
- xo_db.xo.go
という二つのファイルが作成されます。xoで生成したファイルは*.xo.goとなるので分かりやすいですね。
user.xo.goには以下のような内容が生成されています。NOT NULLをつけたか付けないかで型が違っているところに注目して下さい。また、jsonタグも生成されているので、そのままJSONとして出力もできます。
// User represents a row from 'public.users'.
type User struct {
ID int64 `json:"id"` // id
Name sql.NullString `json:"name"` // name
Age int `json:"age"` // age
Weight sql.NullInt64 `json:"weight"` // weight
CreatedAt *time.Time `json:"created_at"` // created_at
UpdatedAt pq.NullTime `json:"updated_at"` // updated_at
// xo fields
_exists, _deleted bool
}
この生成されたUser型に対して、以下の関数が生成されています。
- func (u*User) Exists() bool
- func (u*User) Deleted() bool
- func (u*User) Insert(db XODB) error
- func (u*User) Update(db XODB) error
- func (u*User) Save(db XODB) error
- func (u*User) Delete(db XODB) error
- func (u*User) Upsert(db XODB) error (PostgreSQL 9.5+以上の場合)
XODB型は xo_db.xo.go で定義されているdbに対するinterfaceです。
IDはPrimary Keyですし、nameに対してindexを貼っています。ということで、以下の二つの関数も生成されています。
- func UserByID(db XODB, id int64) (*User, error)
- func UsersByName(db XODB, name sql.NullString) ([]*User, error)
これらの関数を使ってSELECTする、という流れです。UsersByNameの方は、返り値がSliceということもポイントですね。
実装
ここまで自動生成されていればあとは簡単です。以下のような実装がすぐにできます。
db, err := sql.Open("postgres", "dbname=example sslmode=disable")
if err != nil {
panic(err)
}
now := time.Now()
u := &User{
Age: 18,
CreatedAt: &now,
}
err = u.Insert(db)
if err != nil {
panic(err)
}
user, err := UserByID(db, u.ID) // Insertでu.IDがセットされている
if err != nil {
panic(err)
}
fmt.Println(user.Age) // -> 18が返される
SQL
InsertやUpdateなどの関数の中身はどうなってるかというと、
// sql query
const sqlstr = `INSERT INTO public.users (` +
`name, age, weight, created_at, updated_at` +
`) VALUES (` +
`$1, $2, $3, $4, $5` +
`) RETURNING id`
// run query
XOLog(sqlstr, u.Name, u.Age, u.Weight, u.CreatedAt, u.UpdatedAt)
err = db.QueryRow(sqlstr, u.Name, u.Age, u.Weight, u.CreatedAt, u.UpdatedAt).Scan(&u.ID)
if err != nil {
return err
}
というように、SQLがそのまま生成されています。挙動が分かりやすくてぼくはこの方が好きですね。
関数
xoが扱うのはテーブル定義だけではありません。関数も扱ってくれます。
CREATE FUNCTION say_hello(text) RETURNS text AS $$
BEGIN
RETURN CONCAT('hello ' || $1);
END;
$$ LANGUAGE plpgsql;
という関数を定義したとしましょう。こうしておくと、sp_sayhello.xo.goというファイルが生成されます。Stored Procedureですね。
この中にはSayHelloというgolangの関数が定義されています。
- func SayHello(db XODB, v0 string) (string, error)
これ、中身は
// sql query
const sqlstr = `SELECT public.say_hello($1)`
// run query
var ret string
XOLog(sqlstr, v0)
err = db.QueryRow(sqlstr, v0).Scan(&ret)
if err != nil {
return "", err
}
というように、定義した say_hello 関数をSQLで呼ぶようになっています。ですから、
SayHello(db, "hoge")
というように、golangから呼べるようになります。