私が歌川です

@utgwkk が書いている

DBスキーマからGoのstruct定義を生成するグッズを書いた

GoでSQLを書いて実行するとき、素のdatabase/sqlだけだとさすがに心もとないのでsqlxなどのライブラリを使ってDBの行をstructにマッピングすると思います。db struct tagでマッピング元のカラム名を指定できるのが便利ですね。

一方で、このstructを定義する作業ですが、テーブルのカラムが多いと大変だし、typoしていたので直して再チャレンジする……ということが往々にしてあると思います。

こういうときのためにstruct定義を生成するツールを書いてみました。 go install github.com/utgwkk/rowstructgen@latest を実行したら使えるようになると思います。今のところMySQLにしか対応していません (普段はMySQLしか使っていないため)。main.goに全ての実装が書いてあってひどい感じなので、気が向いたらなおします。

github.com

以下のようなDBスキーマが schema.sql というファイル名で用意されていたとします。論理削除には目をつぶってください。

CREATE TABLE `users` (
  `id` BIGINT UNSIGNED NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `deleted_at` DATETIME NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

このようなスキーマに対して、rowstructgenで users テーブルに対応するGoのstruct定義を生成するには以下のようにします。

$ rowstructgen -schema schema.sql -table users -struct User -package row -out row/user.go

こうすると row/user.go に以下のようなファイルが生成されます。

package row

import "time"

type User struct {
    Id        uint64     `db:"id"`
    Name      string     `db:"name"`
    DeletedAt *time.Time `db:"deleted_at"`
    CreatedAt time.Time  `db:"created_at"`
    UpdatedAt time.Time  `db:"updated_at"`
}

ISUCONで用意されているようなDBスキーマならこれぐらいでうまく動くのではないでしょうか。ちゃんとやるならALTER TABLE文をシミュレートする必要がありそうですが、自分の目的を満たすにはこれでじゅうぶんかなーと思っています。

みどころ

sqldefをSQLパーサーとして使う

sqldefというDBスキーマの管理ツールがあります。sqldefについては以下のブログ記事などを読んでください。

k0kubun.hatenablog.com

sqldefはSQLのパーサーを内包しているので、これを使わせてもらいます。これによってDBスキーマをなめてコード生成を行うことに集中できました。

MySQLの型をGoの型にマッピングする

ベタッと書いてあってひどい感じですが、書いてあります。

https://github.com/utgwkk/rowstructgen/blob/bc3221a6d169a4f5c51a0538194bc7b8dfb49b10/main.go#L67

ENUMはstring型にしておくので、必要に応じて独自の型にマップさせましょう、というスタンスです (凝ったことをやりたくないから)。JSONはひとまずbyte型にマッピングしていますがこれも諸説あると思います。ところでJSONってbyteにマッピングできるんでしたっけ?