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

InsertUpdate などの関数の中身はどうなってるかというと、

// 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から呼べるようになります。

まとめ

DBのメタデータからgolangのコードを生成してくれる、 xo を紹介しました。

この他、PostgreSQLで定義した型をgolangの型に変換してくれるなど、かなりいたれりつくせりです。また、コードはtemplateで作られており、このtemplateは自分で定義することもできるので、SQL文を変えたり、関数を追加したりなども自由にできます。

ちょうど 同じようなもの を作ったのですが、xoの方が断然高機能なので、xoを使ったほうが良いかと思います。

Comments

comments powered by Disqus