sqldefのリポジトリ
これは何か
Ridgepoleというツールをご存じでしょうか。
これはRubyのDSLでcreate_table
やadd_index
等を書いてスキーマ定義をしておくとそれと実際のスキーマの差異を埋めるために必要なDDLを自動で生成・適用できる便利なツールです。一方、
で言われているように、Ridgepoleを動作させるためにはRubyやActiveRecordといった依存をインストールする必要があり、Railsアプリケーション以外で使う場合には少々面倒なことになります。*1 *2
そこで、Pure Goで書くことでワンバイナリにし、また別言語圏の人でも使いやすいよう、RubyのDSLのかわりに、誰でも知ってるSQLでCREATE TABLE
やALTER TABLE
を書いて同じことができるようにしたのがsqldefです。
使用例
現時点ではMySQLとPostgreSQLに対応しているのですが、このツールはmysqlコマンドやpsqlコマンドとインターフェースを揃えるため、 それぞれのDBに対しmysqldef、psqldefという別のコマンドを提供しています。
README用にgifアニメを用意しておいたので、こちらで雰囲気を感じてください。
どうやって動いているのか
mysqldef
簡単ですね。go-sql-driver/mysqlというPure GoのMySQLのDBドライバを使っており、mysql(1)やlibmysqlclientに依存していません。
psqldef
これもlib/pqというPostgreSQLのDBドライバがPure Goのため、こちらもpsql(1)やlibpqに依存していません。
pg_dump への依存は、クエリだけでスキーマを取れるようにし、そのうちなくせたらいいなと思ってます。
実装済の機能
- MySQL
- カラムの追加、変更、削除
- テーブルの追加、削除
- インデックスの追加、削除
- 外部キーの追加、削除
- PostgreSQL
- カラムの追加、削除
- テーブルの追加、削除
- インデックスの追加、削除
- 外部キーの追加、削除
あまり無駄にシミュレートをがんばりたくないのと、どうせ対応しても僕は使わないので、CREATE TABLE
, CREATE INDEX
, ALTER TABLE ADD INDEX
の羅列以外の入力に対応していません。DROPは常に書いてあるものを消すことで生成する想定です。テーブルやカラムにリネームが必要な場合は手動でリネームを発行して --export
し直す想定です。
お試しください
まだ本番じゃ全然使えないクオリティなんですが、ISUCONとかでは割と便利に使えるかもしれません。 sqldefがそのまま使えるスキーマ定義が置いてあることが多いようですし。
そういうわけで、よろしくお願いします
追記: schemalexとの比較
schemalexの作者の方にschemalexと比較して欲しいというコメントをいただいているので軽く補足します。
SQL同士を比較してSQLを生成する既存のマイグレーションツールはいろいろあるんですが、その中でもGo製でMySQL向けにスキーマ生成ができるschemalexが既にある中で何故一から作ったかというと、正直なところ完全に調査不足で羃等にスキーマ管理するツールをRidgepole以外に知らなかったことによります。
その上で、2018年8月現時点でsqldefに実装されている機能とschemalexを比較すると、それぞれ主に以下の利点があると思います。
- schemalex
- sqldef
どれもお互い今後の開発次第で解決できる問題ですが、おそらく思想的に変わらなそうなのはそれぞれ最後のCLIに関する部分でしょう。なので、私が羃等なスキーマ管理ツールのユースケースとして想定している以下の状況における、それぞれの現時点での使い方を比較したいと思います。
DBサーバーのスキーマのexport
schemalex
$ echo "" | schemalex -o schema.sql - "mysql://root:@tcp(localhost:3306)/test"
BEGIN;
や COMMIT;
が含まれているのを消す必要があるため、私が把握していないだけでより正しい方法があるかもしれません。
sqldef
$ mysqldef -uroot test --export > schema.sql
Pull Requestマージ後に実行されるDDLの表示、適用
schemalex
もともとgit-schemalexとして開発されていた機能が活用できるため、こちらはschemalexが便利なユースケースだと思います。
# PRチェックアウト時、masterのスキーマ適用のためのDDL表示 $ schemalex "mysql://root:@tcp(localhost:3306)/test" "local-git://.?file=schema.sql&commitish=master" BEGIN; SET FOREIGN_KEY_CHECKS = 0; CREATE TABLE `users` ( `id` BIGINT (20) DEFAULT NULL, `name` VARCHAR (40) DEFAULT NULL ); SET FOREIGN_KEY_CHECKS = 1; COMMIT; # PRチェックアウト時、masterのスキーマの適用 $ schemalex "mysql://root:@tcp(localhost:3306)/test" "local-git://.?file=schema.sql&commitish=master" | mysql -uroot test # マージ後に実行されるDDLの表示 $ schemalex "mysql://root:@tcp(localhost:3306)/test" schema.sql BEGIN; SET FOREIGN_KEY_CHECKS = 0; ALTER TABLE `users` ADD COLUMN `created_at` DATETIME NOT NULL; SET FOREIGN_KEY_CHECKS = 1; COMMIT; # マージ後のスキーマの適用 $ schemalex "mysql://root:@tcp(localhost:3306)/test" schema.sql | mysql -uroot test
sqldef
sqldefは標準でgit連携を持っていないため、mysqlコマンドが不要なかわりにgitコマンドが必要になります。*4
# PRチェックアウト時、masterのスキーマ適用のためのDDL表示 $ git show master:schema.sql | mysqldef -uroot test --dry-run -- dry run -- CREATE TABLE users ( id bigint, name varchar(40) DEFAULT NULL); # PRチェックアウト時、masterのスキーマの適用 $ git show master:schema.sql | mysqldef -uroot test -- Apply -- CREATE TABLE users ( id bigint, name varchar(40) DEFAULT NULL); # マージ後に実行されるDDLの表示 $ mysqldef -uroot test --dry-run < schema.sql -- dry run -- ALTER TABLE users ADD COLUMN created_at datetime NOT NULL; # マージ後のスキーマの適用 $ mysqldef -uroot test < schema.sql -- Apply -- ALTER TABLE users ADD COLUMN created_at datetime NOT NULL;
なおBEGIN;
やCOMMIT;
は表示していませんが、applyはトランザクション下で行なわれます。
最新のスキーマファイルのDBサーバーへの適用
schemalex
# masterチェックアウト時、スキーマ適用のためのDDL表示 $ schemalex "mysql://root:@tcp(localhost:3306)/test" schema.sql BEGIN; SET FOREIGN_KEY_CHECKS = 0; ALTER TABLE `users` ADD COLUMN `created_at` DATETIME NOT NULL; SET FOREIGN_KEY_CHECKS = 1; COMMIT; # スキーマの適用 schemalex "mysql://root:@tcp(localhost:3306)/test" schema.sql | mysql -uroot test
sqldef
# masterチェックアウト時、スキーマ適用のためのDDL表示 $ mysqldef -uroot test --dry-run < schema.sql -- dry run -- ALTER TABLE users ADD COLUMN created_at datetime NOT NULL; $ mysqldef -uroot test < schema.sql -- Apply -- ALTER TABLE users ADD COLUMN created_at datetime NOT NULL;
まとめ
どちらのCLIがユースケースにマッチするかは要件によると思いますが、2018年8月現時点では、MySQLの用途においては実績のあるschemalexを採用するのが現実的だと思います。私自身は自分の自由が効くsqldefをMySQLでも使いメンテを続けるつもりのため、時間が経てばこの問題は解決するでしょう。
一方PostgreSQLでワンバイナリのスキーマ管理ツールが必要な場合は、PostgreSQL未対応のschemalexに対応を入れるよりはsqldefを使ってしまう方が楽かと思われます。
2021-10-30 edit: おかげ様で様々な会社様から本番利用事例をいただいております。まだまだ全てのケースに対応しているとは言えずissueベースでユースケースのカバーを続けている状態ですが、公開から3年以上経った今、schemalexやskeemaと比べても遜色ない利用実績があると言えるでしょう。
言及例:
- 2018-09-05: sqldef本番投入成功
- 2019-12-07: Web開発を支えるマイグレーションツールについて
- 2020-07-30: Nature Remoから学ぶシステムアーキテクチャと開発プロセス w/ songmu *5
- 2020-11-05: ZOZOTOWNリプレイスにおけるSREの取り組み
- 2021-09-27: sqldefへのSQL Server対応のコントリビュート 〜OSS活動を通して紐解くDBマイグレーションツールの実装〜 - ZOZO TECH BLOG
- その他: 利用者が多くを占める sqldef contributors
*1:例えばRuby以外の言語でアプリを書いてCircleCIでテストする場合、CirlceCI公式のDockerイメージは普通に一つの言語しか入ってないので、アプリ用の言語とRidgepole用のRubyが両方入ったDockerイメージを自分で用意しないといけないですよね
*2:Ridgepoleの作者のwinebarrelさんにコメントをいただいてますが、現在はomnibus-rubyによってRubyを同梱したrpmやdebのパッケージとしても配布されているため、バイナリをダウンロードするかわりにパッケージをダウンロードしてインストールする、ということもできそうです。一方手元でmacOSを使っていたりするとrpmやdebは使えないですし、これはItamaeとmitamaeに関しても言える話ですが、一切依存のないバイナリ一つで動作する方が何かと管理が楽であろうという考えのもとこれらのツールを作っています。
*3:schemalexでもこのPR https://github.com/schemalex/schemalex/pull/52 で同様の機能が実装されると理解していますが、未マージのようです
*4:GitHubにリポジトリを置いていたらCIからcloneしてくる際に必要になるので、普通は入ってるとは思います
*5:00:25:10~: "多分sqldefの方が使われていると思うんですけど" と言及いただきました