背景
GitBucketのマルチDB対応(とりあえずH2の代わりにMySQLを使えるようにすることを目指しています)にあたり、バージョンアップ時のマイグレーションシステムも複数のDBに対応する必要があります。現在のGitBucketではFlywayに似た独自のSQLベースのマイグレーションシステムを搭載しているのですが、マルチDB対応するからといっていちいちDBの種類別にSQLを書きわけるなどめんどくさくてやってられません。
そこで、抽象的なDSLでマイグレーション内容を記述しておくとDBの種類に応じてSQLを生成してくれるマイグレーションツールはないかなと思い探してみたところ、Scalaから使えそうなものとして以下の2つが上がりました。
scala-migrationsはScalaのDSLで書けてよさそうなのですが、最近メンテされてなさげなのと、H2に対応していないこともあり、Liquibaseを試してみることにしました。
ただし、GitBucketでは場合によってはDBだけでなくGitリポジトリやアップロードされたファイルなどに対するマイグレーションが発生しますし、プラグインに対してもマイグレーション機能を提供する必要があるなど状況が複雑で、恐らくLiquibaseをそのまま組み込むだけでは要件を満たせない可能性が高いため、LiquibaseのSQL生成部分のみ使用してマイグレーションシステムは自作することが可能かどうかを調べてみました。
やってみる
まずはbuild.sbt
に以下の依存関係を追加します。
"org.liquibase" % "liquibase-core" % "3.4.1"
こんな感じのXMLを用意しておきます。ここではtest.xml
としてカレントディレクトリに保存しておきます(XMLでなくJSONやYAMLで記述することも可能です)。
<?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> <preConditions> <runningAs username="liquibase"/> </preConditions> <changeSet id="1" author="nvoxland"> <createTable tableName="person"> <column name="id" type="int" autoIncrement="true"> <constraints primaryKey="true" nullable="false"/> </column> <column name="firstname" type="varchar(50)"/> <column name="lastname" type="varchar(50)"> <constraints nullable="false"/> </column> <column name="state" type="char(2)"/> </createTable> </changeSet> <changeSet id="2" author="nvoxland"> <addColumn tableName="person"> <column name="username" type="varchar(8)"/> </addColumn> </changeSet> <changeSet id="3" author="nvoxland"> <addLookupTable existingTableName="person" existingColumnName="state" newTableName="state" newColumnName="id" newColumnDataType="char(2)"/> </changeSet> </databaseChangeLog>
このマイグレーションファイルからOracle用のSQLを生成するScalaプログラムを書いてみます。
import liquibase._ import liquibase.database.core._ import liquibase.resource.FileSystemResourceAccessor import liquibase.sqlgenerator.SqlGeneratorFactory import scala.collection.JavaConverters._ val database = new OracleDatabase() val liquibase = new Liquibase("test.xml", new FileSystemResourceAccessor(), database) val changelog = liquibase.getDatabaseChangeLog changelog.getChangeSets.asScala.foreach { changeset => // チェンジセットのIDを表示 println("-- " + changeset.getId) // チェンジセット内の変更それぞれについてSQLを表示 changeset.getChanges.asScala.foreach { change => val stmt = change.generateStatements(database) val sqls = SqlGeneratorFactory.getInstance.generateSql(stmt, database) sqls.foreach { sql => println(sql.toSql) } } }
こんな感じの出力が得られます。
-- 1 CREATE TABLE person (id NUMBER(10) GENERATED BY DEFAULT AS IDENTITY NOT NULL, firstname VARCHAR2(50), lastname VARCHAR2(50) NOT NULL, state CHAR(2), CONSTRAINT PK_PERSON PRIMARY KEY (id)) -- 2 ALTER TABLE person ADD username VARCHAR2(8) -- 3 CREATE TABLE state AS SELECT DISTINCT state AS id FROM person WHERE state IS NOT NULL ALTER TABLE state ADD PRIMARY KEY (id) ALTER TABLE person ADD CONSTRAINT FK_PERSON_STATE FOREIGN KEY (state) REFERENCES state (id)
OracleDatabase
のところをMySQLDatabase
とかH2Database
とかに変更すればそれぞれのデータベース用のSQLが生成されます。
これならGitBucketのマイグレーションシステムに組み込むことができそうです。パッケージソフトなど様々なDBに対応する必要があるような用途でのマイグレーションにも使えるのではないでしょうか。