読者です 読者をやめる 読者になる 読者になる

LiquibaseのSQL生成部分をScalaから使う

Scala

背景

GitBucketのマルチDB対応(とりあえずH2の代わりにMySQLを使えるようにすることを目指しています)にあたり、バージョンアップ時のマイグレーションシステムも複数のDBに対応する必要があります。現在のGitBucketではFlywayに似た独自のSQLベースのマイグレーションシステムを搭載しているのですが、マルチDB対応するからといっていちいちDBの種類別にSQLを書きわけるなどめんどくさくてやってられません。

そこで、抽象的なDSLマイグレーション内容を記述しておくとDBの種類に応じてSQLを生成してくれるマイグレーションツールはないかなと思い探してみたところ、Scalaから使えそうなものとして以下の2つが上がりました。

scala-migrationsはScalaDSLで書けてよさそうなのですが、最近メンテされてなさげなのと、H2に対応していないこともあり、Liquibaseを試してみることにしました。

ただし、GitBucketでは場合によってはDBだけでなくGitリポジトリやアップロードされたファイルなどに対するマイグレーションが発生しますし、プラグインに対してもマイグレーション機能を提供する必要があるなど状況が複雑で、恐らくLiquibaseをそのまま組み込むだけでは要件を満たせない可能性が高いため、LiquibaseのSQL生成部分のみ使用してマイグレーションシステムは自作することが可能かどうかを調べてみました。

やってみる

まずはbuild.sbtに以下の依存関係を追加します。

"org.liquibase" % "liquibase-core" % "3.4.1"

こんな感じのXMLを用意しておきます。ここではtest.xmlとしてカレントディレクトリに保存しておきます(XMLでなくJSONYAMLで記述することも可能です)。

<?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に対応する必要があるような用途でのマイグレーションにも使えるのではないでしょうか。