SQLで羃等にDBスキーマ管理ができるツール「sqldef」を作った

sqldefのリポジトリ

github.com

これは何か

Ridgepoleというツールをご存じでしょうか。 これはRubyDSLcreate_tableadd_index等を書いてスキーマ定義をしておくとそれと実際のスキーマの差異を埋めるために必要なDDLを自動で生成・適用できる便利なツールです。一方、

f:id:k0kubun:20180825111557p:plain:w400

で言われているように、Ridgepoleを動作させるためにはRubyActiveRecordといった依存をインストールする必要があり、Railsアプリケーション以外で使う場合には少々面倒なことになります。*1

そこで、Pure Goで書くことでワンバイナリにし、また別言語圏の人でも使いやすいよう、RubyDSLのかわりに、誰でも知ってるSQLCREATE TABLEALTER TABLEを書いて同じことができるようにしたのがsqldefです。

使用例

現時点ではMySQLPostgreSQLへの対応を目指しているのですが、このツールはmysqlコマンドやpsqlコマンドとインターフェースを揃えるため、 それぞれのDBに対しmysqldef、psqldefという別のコマンドを提供しています。

README用にgifアニメを用意しておいたので、こちらで雰囲気を感じてください。

f:id:k0kubun:20180825111815g:plain

どうやって動いているのか

mysqldef

  1. show tables; と show create table xxx; を発行して現在のスキーマを取得
  2. 入力のDDLとインクリメンタルに比較を行ない、必要なDDLを生成
  3. 生成したDDLを実行


簡単ですね。go-sql-driver/mysqlというPure GoのMySQLのDBドライバを使っており、mysql(1)やlibmysqlclientに依存していません。

psqldef

  1. pg_dump コマンドを実行して現在のスキーマを取得
  2. 入力のDDLとインクリメンタルに比較を行ない、必要なDDLを生成
  3. 生成したDDLを実行


これもlib/pqというPostgreSQLのDBドライバがPure Goのため、こちらもpsql(1)やlibpqに依存していません。 pg_dump への依存は、クエリだけでスキーマを取れるようにし、そのうちなくせたらいいなと思ってます。

実装済の機能

  • MySQL
    • カラムの追加、削除
    • テーブルの追加、削除
    • インデックスの追加、削除
  • PostgreSQL
    • カラムの追加、削除
    • テーブルの追加、削除

MySQLでの動作は多少遊べるくらいの水準で、PostgreSQLは僕があんまり詳しくないのと使ってるSQLパーサーの関係もあって割と壊れてる状態ですが、どちらもそのうちいい感じにするつもりです。カラムやインデックスの種類の変更の反映とか、外部キーの対応とか。

また、あまり無駄にシミュレートをがんばりたくないのと、どうせ対応しても僕は使わないので、CREATE TABLEと一部のALTER TABLEの羅列以外の入力に対応していません。DROPは常に書いてあるものを消すことで生成する想定です。

現状の問題点

vitessio/vitessというツールの内部から切り出されたxwb1989/sqlparserというSQLパーサを使っているのですが、どうもこれがDDLのパースのサポートが弱く、そもそもこのツール向きじゃないようです。

ADD INDEXもパースできなくて致命的なので既にフォークしてるんですが、パースエラーが発生してもerrorを返さない(ログを吐くだけ)インターフェースな上、エラーになった状態でASTにアクセスするとSEGVしてしまいます。特にPostgreSQLだと割と簡単な正常系でもこれを引く状態です。MySQLでも型名をtypoするとこれになります。

なのでまあ、SQLパーサはDDLに特化した簡単な奴を作り直して内部に持つようにしようと考えています。

お試しください

まだ本番じゃ全然使えないクオリティなんですが、ISUCONがMySQLで出題された場合とかは割と便利に使えるかもしれません。 sqldefがそのまま使えるスキーマ定義が置いてあることが多いようですし。*2

そういうわけで、よろしくお願いします

github.com

*1:例えばRuby以外の言語でアプリを書いてCircleCIでテストする場合、CirlceCI公式のDockerイメージは普通に一つの言語しか入ってないので、アプリ用の言語とRidgepole用のRubyが両方入ったDockerイメージを自分で用意しないといけないですよね

*2:実際にはsqlparserがアレなのでAUTO_INCREMENTの位置を変えたりしないと微妙にパーサに文句を言われますが、--exportをし直せば大体動きます