Database Migration Pluginでcontextを使用する

LiquibaseではchangeSetにcontext属性が設定できます。このcontext属性を使用することで、実行時に適用するchangeSetの範囲を指定できます。

databaseChangeLog = {

    changeSet(author: "yamkazu", id: "default-ctx") {
        ...
    }

    changeSet(author: "yamkazu", id: "prod-ctx", context: "prod") {
        ...
    }

    changeSet(author: "yamkazu", id: "test-ctx", context: "test") {
        ...
    }
}

実行時にcontextを指定するには--contextsオプションを使用します。

grails> dbm-update --contexts="test"

いくつかの特徴的なルールがあるので以下にまとめます。

  • 複数指定する場合はカンマで区切る
  • changeSetでcontextが無指定の場合は常に実行される
  • --contextsを指定しないとすべてのコンテキストが対象になる

後ろ2つが若干わかりにくいですが、--contextsの指定と実行されるidは以下のようになります。

  • dbm-update - default-ctxtest-ctxtest-ctxのchangeSetを実行
  • dbm-update --contexts="test" - default-ctxtest-ctxのchangeSetを実行
  • dbm-update --contexts="test,prod" - default-ctxtest-ctxtest-ctxのchangeSetを実行

特に指定しないとすべてのコンテキスト実行される点は注意が必要です。例えばtestのようなコンテキストを作成した時点で、本番環境に適用する際には明示的に何かのコンテキストを指定しないと、testを含む全てのchangeSetが適用されるといった動作をします。この場合は実際にそのコンテキストが存在するかは別として、明示的に何らかの適当なコンテキストを指定しておく必要があります。

updateOnStartでアプリケーション起動時に連動させるにはConfig.groovyでgrails.plugin.databasemigration.updateOnStartContextを指定してください。

grails.plugin.databasemigration.updateOnStartContext="prod"

Database Migration Pluginで毎回実行するchangelogを定義する

runAlways属性をtrueに設定することで毎回実行するchangelogを定義できます。runOnChange属性がtrueの場合ではchangesetのチェックサムが変更になった時のみ実行しますが、runAlways属性をtrueにするとチェックサム変更有無に関係なく毎回実行してくれます。

changeSet(author: "yamkazu", id: "create-dummy-data", runAlways: true, runOnChange: true) {
    sql("DELETE FROM person")
    sql("""
        |INSERT INTO person ( id, version, name ) VALUES ( nextval('hibernate_sequence'), 0, 'tanaka' );
        |INSERT INTO person ( id, version, name ) VALUES ( nextval('hibernate_sequence'), 0, 'sato' );
        """.stripMargin())
}

リファレンスを読む限り

Executes the change set on every run, even if it has been run before

と記述されておりrunAlwaysだけ付与すれば動くように読めますが現時点(Database Migration Plugin 1.3.2、Liquibase 2.0.5)ではrunOnChangeをtrueにしないとチェックサムのエラーになります。

こんな投稿を発見したが、いまいち直感的じゃない気がします。

Database Migration PluginでNotNull制約のカラムを追加する

既存のデータが存在する場合に、NotNull制約が付与されたカラムを追加する場合は少し工夫が必要です。単にカラムを追加すると既存のデータがNULLになってしまうためエラーとなります。これを回避するには一度NotNull制約を付与せずにカラムを追加し、既存データに対してUPDATEをかけた上で、NotNull制約を追加してあげる必要があります。

以下のドメインがあるとします。

class Person {
    String name
}

以下のchangesetでデータベースと同期済みであるとします。

changeSet(author: "yamkazu (generated)", id: "1362294228819-1") {
    createTable(tableName: "person") {
        column(name: "id", type: "int8") {
            constraints(nullable: "false", primaryKey: "true", primaryKeyName: "personPK")
        }

        column(name: "version", type: "int8") {
            constraints(nullable: "false")
        }

        column(name: "name", type: "varchar(255)") {
            constraints(nullable: "false")
        }
    }
}

さらにデータベースには以下のデータが入っているとします。

 id | version |  name
----+---------+--------
  1 |       0 | yamada
  2 |       0 | sato

単純にカラムを追加するとエラーとなる

ドメインにageのプロパティを追加します。

class Person {
    String name
    Integer age
}

この状態でdbm-gorm-diffコマンドを使用すると以下のようなchangelogを生成します。

changeSet(author: "yamkazu (generated)", id: "1362294947235-1") {
    addColumn(tableName: "person") {
        column(name: "age", type: "int4") {
            constraints(nullable: "false")
        }
    }
}

このchangesetを反映するためにdbm-updateを実行します。

| Error 2013-03-03 16:19:10,773 [main] ERROR liquibase  - Change Set changelog-0.1.groovy::1362294947235-1::yamkazu (generated) failed.  Error: Error executing SQL ALTER TABLE person ADD age int4 NOT NULL: ERROR: column "age" contains null values
Message: Error executing SQL ALTER TABLE person ADD age int4 NOT NULL: ERROR: column "age" contains null values

期待した通りエラーとなりました。

addNotNullConstraintを使用する

エラーを回避するためには、はじめに記述したように一度NotNull制約を付与せずにカラムを追加し、既存データに対してUPDATEをかけた上で、NotNull制約を追加します。NotNull制約を追加するにはaddNotNullConstraintが使用できます。

addNotNullConstraintの詳細はリファレンスを参照してください。

changeSet(author: "yamkazu (generated)", id: "1362294947235-1") {
    addColumn(tableName: "person") {
        column(name: "age", type: "int4")
    }
    sql("UPDATE person SET age = 30")
    addNotNullConstraint(tableName: "person", columnName: "age")
    rollback {
        dropColumn(tableName: "person", columnName: "age")
    }
}

addColumnconstraints(nullable: "false")とせず、(年齢を一律30才としていいかはおいといて)一度値を設定した後に、addNotNullConstraintを使用してNotNull制約を追加しています。

rollbackはchangeSet配下に複数のコマンドがある場合は自動でロールバック処理を作成しません。自動生成させるためにchangeSetをコマンド毎に分けるという案もありますが、ここではグループ化して、明示的にroolbackを指定しています。

addNotNullConstraintのdefaultNullValueを使用する

上記では明示的にUPDATEsqlコマンドを使用して設定しましたが、単純な値セットだけならばaddNotNullConstraintのdefaultNullValueが使用できます。

changeSet(author: "yamkazu (generated)", id: "1362294947235-1") {
    addColumn(tableName: "person") {
        column(name: "age", type: "int4")
    }
    addNotNullConstraint(tableName: "person", columnName: "age", defaultNullValue: "30")
    rollback {
        dropColumn(tableName: "person", columnName: "age")
    }
}

defaultNullValueを使用すると以下のことを自動でやってくれます。

UPDATE person SET age = '30' WHERE age IS NULL;
ALTER TABLE person ALTER COLUMN  age SET NOT NULL;

単純な値セットであればdefaultNullValueで十分ですが、他のテーブル、カラムから値を算出するといった場合には使用できないため、その場合は先程のsqlコマンドなどを使用してください。

Database Migration Pluginで任意のSQLを実行する

Database Migration PluginではLiquibaseで使用可能なchangesetのコマンドが、groovyフォーマットのchangesetでも同様に使用可能になっています。

使用可能なコマンドの一覧はLiquibaseのリファレンスを参照してください。

今日はこの中からCustom SQLCustom SQL Fileについて紹介します。

Custom SQL

Custom SQLは任意のSQLを実行するコマンドです。

changeSet(author: "yamkazu (generated)", id: "create-person") {
    sql("CREATE TABLE person ( id int8 primary key, name varchar(255) )")
    sql("""
        |INSERT INTO person ( id, name ) VALUES ( 1, 'tanaka' );
        |INSERT INTO person ( id, name ) VALUES ( 2, 'sato' );
        """.stripMargin())
    sql([stripComments: true, splitStatements: false],
        """
        |INSERT INTO person ( id, name ) VALUES ( 3, 'suzuki' );   -- insert suzuki
        |INSERT INTO person ( id, name ) VALUES ( 4, 'yamamoto' ); -- insert yamamoto
        """.stripMargin())
    rollback {
        sql("DELETE FROM person")
        dropTable(tableName: "person")
    }
}

例を見ればだいたい使い方が想像できると思います。

一番シンプルな使い方はsql("...")形式で実行したいSQLを指定するだけです。

属性を付与しつつ内容を記述する場合はsql([stripComments: true, splitStatements: false],"…")のように第1引数にmapで属性を指定し、第2引数にクエリーの文字列を指定します。いくつか属性がありますがstripComments属性にtrueを指定するとコメントが削除され、splitStatementsfalseを指定すると;でステートメントが分割されず、ひとつの1つのステートメントとして実行されます。

sqlコマンドを使用する際の注意点ですが、自動ロールバックに対応していないということです。createTableといったコマンドではデフォルトでそれに対するロールバックが定義されていますが、sqlではそれがないため明示的にrollbackコマンドを使用して、このchangeSetをロールバックする処理を記述する必要があります。

roolbackは直接SQLを記述したりchangeSetで使用可能なコマンドが使用できます。詳細はリファレンスを参照してください。

Custom SQL File

Custom SQL Fileは任意のSQLファイルを実行できます。

changeSet(author: "yamkazu (generated)", id: "create-person") {
    sqlFile(path: "create-person.sql")
    sqlFile(path: "sql/person-data-1.sql")
    sqlFile(path: "/sql/person-data-2.sql", stripComments: true, splitStatements: false)
    rollback {
        dropTable(tableName: "person")
    }
}

pathにファイルを指定することで使用できます。ファイルはクラスパスから読みだされchangelog.groovyを起点としてた相対パス、または絶対パスが使用できます。

Custom SQLと同様にCustom SQL Fileも自動ロールバックには対応していないため、自身でロールバック処理を記述する必要があります。

Database Migration Plugin のチェックサム

データベースへの反映履歴を管理するdatabasechangelogテーブルにはmd5sumというchangesetチェックサムを格納するカラムがあります。

liquibaseのリファレンスには以下のように記述されています。

LiquiBase が変更セットに到達すると、MD5Sum を計算して、”databasechangelog” に MD5Sum を保存します。MD5Sum を保存する意味は、LiquiBase が、ほかの誰かが実行されて以来変更セットを変更していないかどうかを知ることができるためです。変更セットが実行されたときから変更されていた場合、LiquiBase はエラーとともに移行を終了します。というのも、何が変更されたか知ることができず、データベースが変更ログが期待しているのと異なった状態にあるかもしれないからです。もし、適切な理由によって変更セットが変更されていた場合やこのエラーを無視したい場合は、databasechangelog テーブルを更新して、その行の id/author/ファイルのパス名に対応する MD5Sum を null に更新します。次回 LiquiBase が実行されると、MD5Sum の値を適切な値に更新してくれます。

MD5Sum は、”runOnChange” 変更セット属性と一緒に使用されます。普通はただ現在のバージョンが知りたいだけで新しい変更セットを追加したくないのに、更新されたときはいつでも適用したいときがなんどもあるでしょう。このよい例はストアドプロシージャに関するものです。ストアドプロシージャの全体をコピーして新しい変更セットを作るたび、とても長い変更ログが無駄に終わるだけでなく、ソースコード管理システムのマージや差分の機能を失うことになるのです。代わりに、runOnChange = “true” 属性を変更セットにあるストアドプロシージャのテキストにつけましょう。そのストアドプロシージャは、内容が変更されたときだけ再作成されるようになります。

簡単に言うと、changesetを変更しチェックサムが変わった場合に、前者のデフォルトの場合(runOnChange=false)はエラー、runOnChange=trueの場合は再度実行という動作の違いがあるようです。

書いてある通りなのですが実際に動作させながら検証してみます。

デフォルトの場合

以下のPersonドメインがあるとします。

class Person {
    String name
}

このドメインに対するchangelogを以下のように作成します。

databaseChangeLog = {

    changeSet(author: "yamkazu (generated)", id: "create-person") {
        createTable(tableName: "person") {
            column(name: "id", type: "int8") {
                constraints(nullable: "false", primaryKey: "true", primaryKeyName: "personPK")
            }

            column(name: "version", type: "int8") {
                constraints(nullable: "false")
            }

            column(name: "name", type: "varchar(255)") {
                constraints(nullable: "false")
            }
        }
    }
}

この状態で一度dbm-updateコマンドを使用してデータベースと同期しておきます。同期が成功すると上のchangesetを反映した履歴がdatabasechangelogが書き込まれます。

devDb=# select id, md5sum from databasechangelog;
      id       |               md5sum
---------------+------------------------------------
 create-person | 3:c2a46c4edd51cd911c9dced0a8fcabe3

changeSetの内容を元にmd5sumが書き込まれています。この状態でほど作成したchangeSetのnameカラムの型をvarchar(255)からtextに変更します。

databaseChangeLog = {

    changeSet(author: "yamkazu (generated)", id: "create-person") {
        createTable(tableName: "person") {
            ...
            column(name: "name", type: "text") {
                constraints(nullable: "false")
            }
        }
    }
}

同じIDにもかかわらずchangeSetの中身が書き換えられた状態です。この状態でdbm-validateコマンドを実行します。

grails> dbm-validate
| Starting dbm-validate for database test @ jdbc:postgresql://localhost:5432/devDb
Validation Error:
     1 change sets have changed since they were ran against the database
          changelog-0.1.groovy::create-person::yamkazu (generated)

このようにchangeSetの中身が変わりチェックサムがことなるためエラーとなります。dbm-updateなどの同期処理においても必ず最初にこのvalidateが行われるため、同様にエラーになります(1.3.2までこのエラーがdbm-validateコマンド以外では握りつぶされていましたが1.3.3で修正される予定です)。

textへの変更をvarchar(255)に戻して、次はドメインに変更を加えてみます。

class Person {
    String name
    Integer age
}

ageのプロパティを追加しました。このカラムを追加するchangesetを以下のように作成します。

databaseChangeLog = {
    ...
    changeSet(author: "yamkazu (generated)", id: "add-age") {
        addColumn(tableName: "person") {
            column(name: "age", type: "int4") {
                constraints(nullable: "false")
            }
        }
    }
}

再度dbm-updateコマンドを使用してデータベースと同期してします。databasechangelogは以下のようになります。

devDb=# select id, md5sum from databasechangelog;
      id       |               md5sum
---------------+------------------------------------
 create-person | 3:c2a46c4edd51cd911c9dced0a8fcabe3
 add-age       | 3:2e304646a134a175a22769177941c06a

すでにデータベースに反映が完了して、changesetのcreate-personadd-ageを正規化して一つにまとめたいと考えたとします。

databaseChangeLog = {

    changeSet(author: "yamkazu (generated)", id: "create-person") {
        createTable(tableName: "person") {
            column(name: "id", type: "int8") {
                constraints(nullable: "false", primaryKey: "true", primaryKeyName: "personPK")
            }

            column(name: "version", type: "int8") {
                constraints(nullable: "false")
            }

            column(name: "name", type: "varchar(255)") {
                constraints(nullable: "false")
            }

            column(name: "age", type: "int4") {
                constraints(nullable: "false")
            }
        }
    }
}

create-personのchangesetにageを追加し、add-ageは削除しました。この状態でdbm-updateを実施すると当然エラーになります。チャックサムが異なるためです。

Validation Error:
     1 change sets have changed since they were ran against the database
          changelog-0.1.groovy::create-person::yamkazu (generated)

特にデータベースへ反映を行うわけではないがchangesetだけ正規化するとこのような状態になります。この状態を解消するには一度dbm-clear-checksumを実行します。

grails> dbm-clear-checksums

databasechangelogは以下のようになります。

devDb=# select id, md5sum from databasechangelog;
      id       | md5sum
---------------+--------
 create-person |
 add-age       |

このようにmd5sumの値がクリアされます。この状態で再度dbm-updateを実施するとdatabasechangelogは以下のようになります。

devDb=# select id, md5sum from databasechangelog order by orderexecuted ;
      id       |               md5sum
---------------+------------------------------------
 create-person | 3:393ba7bc4288b805a68f49b8c3f3f3ee
 add-age       |

今度はエラーとならずにcreate-personに新たなハッシュ値が書き込まれました。 ちなみにこの時にdbm-rollback-count-sql 1を実行すると以下になります。

grails> dbm-rollback-count-sql 1
DROP TABLE person;

DELETE FROM databasechangelog  WHERE ID='create-person' AND AUTHOR='yamkazu (generated)' AND FILENAME='changelog-0.1.groovy';

どうも実行のsqlを見ているとmd5sumがnullのレコードを対象として動作するようです。少し注意が必要です。 このsqlロールバックするとadd-ageが残ってしまいますが、特に残っていても害はなさそうですが気になります。dbm-updateのオプションなどで再度チェックサム書いたあとに浮いたレコード削除してくれてもいいようなきもしますが、そんな機能は今のところないようです。

runOnChange=true の場合

runOnChange=trueはchangeSetの属性に設定します。注意点としては当たり前ですがrunOnChange=trueで再実行された際に問題なく動作するクエリを書くことです。

changeSet(author: "yamkazu (generated)", id: "create-one-function", runOnChange: true) {
    sql("""
        | CREATE OR REPLACE FUNCTION one() RETURNS integer
        |    AS 'SELECT 1 AS RESULT;'
        |    LANGUAGE SQL;
        """.stripMargin())
}

CREATE OR REPLACEというふうに宣言することで何度実行しても問題ありません。これでfunctionの中の実装が変わると再度実行されます。

Database Migration Pluginがすごい ~ロールバック編~

前回の続き

Database Migration Pluginのすごいところはロールバックが出来る事。ロールバックを行うコマンドは主に3つ種類がある。

  • dbm-rollback-count
  • dbm-rollback-to-date
  • dbm-rollback

それぞれ個別に見ていく。

dbm-rollback-count

dbm-rollback-countは指定した数分の変更履歴をロールバックするコマンド。例えばdatabasechangelogの状態が以下のようになっていたとする。

devDb=> select * from databasechangelog;
       id        |       author        |     filename      |         dateexecuted          | orderexecuted | exectype |               md5sum               |        description         | comments | tag | liquibase 
-----------------+---------------------+-------------------+-------------------------------+---------------+----------+------------------------------------+----------------------------+----------+-----+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table               |          |     | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence            |          |     | 2.0.5
 1351333217527-1 | yamkazu (generated) | add-author.groovy | 2012-10-27 19:27:06.174495+09 |             3 | EXECUTED | 3:500d2782c8ddcc7d7b89b0b29b2d7342 | Create Table               |          |     | 2.0.5
 1351333217527-2 | yamkazu (generated) | add-author.groovy | 2012-10-27 19:27:06.190441+09 |             4 | EXECUTED | 3:d4dcaaa9285f777d2a05f33e275488b7 | Add Column                 |          |     | 2.0.5
 1351333217527-3 | yamkazu (generated) | add-author.groovy | 2012-10-27 19:27:06.200821+09 |             5 | EXECUTED | 3:77e6c768bd70db135e30edfb2ef6788e | Add Foreign Key Constraint |          |     | 2.0.5

ここでbookだけがあった状態に戻したいといった場合、最新の3つのchangesetを打ち消す必要がある。ここでdbm-rollback-count-sqlというコマンドを実行してみる。

これはロールバック系のコマンドだけでなく他のコマンドにも用意されて、xxx-sqlの形になっている。これを使用すると、xxxのコマンドを実行した時に実際にどんなSQLが実行されるのか確認出来る。また、grailsコマンドが使用できないような環境にシステムがあるばあはこのSQLを持って行く事もできる。

grails> dbm-rollback-count-sql 3
| Starting dbm-rollback-count-sql for database test @ jdbc:postgresql://localhost:5432/devDb
-- *********************************************************************
-- Rollback 3 Change(s) Script
-- *********************************************************************
-- Change Log: changelog.groovy
-- Ran at: 12/10/28 17:09
-- Against: test@jdbc:postgresql://localhost:5432/devDb
-- Liquibase version: 2.0.5
-- *********************************************************************

-- Lock Database
-- Rolling Back ChangeSet: add-author.groovy::1351333217527-3::yamkazu (generated)::(Checksum: 3:77e6c768bd70db135e30edfb2ef6788e)
ALTER TABLE book DROP CONSTRAINT FK2E3AE9B2F6003C;

DELETE FROM databasechangelog  WHERE ID='1351333217527-3' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-2::yamkazu (generated)::(Checksum: 3:d4dcaaa9285f777d2a05f33e275488b7)
ALTER TABLE book DROP COLUMN author_id;

DELETE FROM databasechangelog  WHERE ID='1351333217527-2' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-1::yamkazu (generated)::(Checksum: 3:500d2782c8ddcc7d7b89b0b29b2d7342)
DROP TABLE author;

DELETE FROM databasechangelog  WHERE ID='1351333217527-1' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

| Finished dbm-rollback-count-sql

changesetをぞれぞれ評価していって、そのchangesetの変更を取り返すSQLが出力さているのがわかる。問題無さそうなので実際に実行。

grails> dbm-rollback-count 3
| Finished dbm-rollback-count

DBを確認してみる。

devDb=> \d
                 List of relations
 Schema |         Name          |   Type   | Owner 
--------+-----------------------+----------+-------
 public | book                  | table    | test
 public | databasechangelog     | table    | test
 public | databasechangeloglock | table    | test
 public | hibernate_sequence    | sequence | test
(4 rows)

devDb=> select * from databasechangelog;
       id        |       author        |    filename     |         dateexecuted          | orderexecuted | exectype |               md5sum               |   description   | comments | tag | liquibase 
-----------------+---------------------+-----------------+-------------------------------+---------------+----------+------------------------------------+-----------------+----------+-----+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table    |          |     | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence |          |     | 2.0.5
(2 rows)

ちゃんとロールバックされている。

dbm-rollback-to-date

次はdbm-rollback-to-dateで指定した時間までロールバックするというもの。DBの状態が以下のようになっていたとする。dateexecutedの日時に対して判断される。

devDb=> select * from databasechangelog;
       id        |       author        |     filename      |         dateexecuted          | orderexecuted | exectype |               md5sum               |        description         | comments | tag | liquibase 
-----------------+---------------------+-------------------+-------------------------------+---------------+----------+------------------------------------+----------------------------+----------+-----+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table               |          |     | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence            |          |     | 2.0.5
 1351333217527-1 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:17:07.623519+09 |             3 | EXECUTED | 3:500d2782c8ddcc7d7b89b0b29b2d7342 | Create Table               |          |     | 2.0.5
 1351333217527-2 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:17:07.639352+09 |             4 | EXECUTED | 3:d4dcaaa9285f777d2a05f33e275488b7 | Add Column                 |          |     | 2.0.5
 1351333217527-3 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:17:07.648655+09 |             5 | EXECUTED | 3:77e6c768bd70db135e30edfb2ef6788e | Add Foreign Key Constraint |          |     | 2.0.5

ここでauthorが追加される前の状態まで戻りたいとする。日付と時刻はそれぞれ、yyyy-MM-dd、 HH:mm:ssで指定する。時刻はオプションで指定しなかった場合は00:00:00が指定されたものと同じとなる。

先ほどと同じようにdbm-rollback-to-date-sqlで事前確認

grails> dbm-rollback-to-date-sql 2012-10-28
Starting dbm-rollback-to-date-sql for database test @ jdbc:postgresql://localhost:5432/devDb-- *********************************************************************-- Rollback to Sun Oct 28 00:00:00 JST 2012 Script
-- *********************************************************************
-- Change Log: changelog.groovy
-- Ran at: 12/10/28 17:24-- Against: test@jdbc:postgresql://localhost:5432/devDb-- Liquibase version: 2.0.5-- *********************************************************************-- Lock Database-- Rolling Back ChangeSet: add-author.groovy::1351333217527-3::yamkazu (generated)::(Checksum: 3:77e6c768bd70db135e30edfb2ef6788e)
ALTER TABLE book DROP CONSTRAINT FK2E3AE9B2F6003C;

DELETE FROM databasechangelog  WHERE ID='1351333217527-3' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-2::yamkazu (generated)::(Checksum: 3:d4dcaaa9285f777d2a05f33e275488b7)
ALTER TABLE book DROP COLUMN author_id;

DELETE FROM databasechangelog  WHERE ID='1351333217527-2' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-1::yamkazu (generated)::(Checksum: 3:500d2782c8ddcc7d7b89b0b29b2d7342)
DROP TABLE author;

DELETE FROM databasechangelog  WHERE ID='1351333217527-1' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

| Finished dbm-rollback-to-date-sql

問題無さそう。では実行。

grails> dbm-rollback-to-date 2012-10-28
| Finished dbm-rollback-to-date

DBを確認。

devDb=> \d
                 List of relations
 Schema |         Name          |   Type   | Owner 
--------+-----------------------+----------+-------
 public | book                  | table    | test
 public | databasechangelog     | table    | test
 public | databasechangeloglock | table    | test
 public | hibernate_sequence    | sequence | test
(4 rows)

devDb=> select * from databasechangelog;
       id        |       author        |    filename     |         dateexecuted          | orderexecuted | exectype |               md5sum               |   description   | comments | tag | liquibase 
-----------------+---------------------+-----------------+-------------------------------+---------------+----------+------------------------------------+-----------------+----------+-----+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table    |          |     | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence |          |     | 2.0.5
(2 rows)

うまくいっている。

dbm-rollback

最後にdbm-rollback。これは指定したタグまでロールバックするというもの。そもそもタグとは何か。

タグはdbm-tagコマンドで設定できる。現在のDB状態に名前が付けられる。DB状態が以下のようになっていたとする。

devDb=> select * from databasechangelog;
       id        |       author        |    filename     |         dateexecuted          | orderexecuted | exectype |               md5sum               |   description   | comments | tag | liquibase 
-----------------+---------------------+-----------------+-------------------------------+---------------+----------+------------------------------------+-----------------+----------+-----+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table    |          |     | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence |          |     | 2.0.5

この状態でdbm-tagを実行する。現状のDBの状態にv1.0という名前のタグを付けてみる。

grails> dbm-tag v1.0
| Finished dbm-tag

DBを再確認。

devDb=> select * from databasechangelog;
       id        |       author        |    filename     |         dateexecuted          | orderexecuted | exectype |               md5sum               |   description   | comments | tag  | liquibase 
-----------------+---------------------+-----------------+-------------------------------+---------------+----------+------------------------------------+-----------------+----------+------+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table    |          |      | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence |          | v1.0 | 2.0.5
(2 rows)

tagのところにv1.0が入っている。この後DBの変更があって以下になったとする。

devDb=> select * from databasechangelog;
       id        |       author        |     filename      |         dateexecuted          | orderexecuted | exectype |               md5sum               |        description         | comments | tag  | liquibase 
-----------------+---------------------+-------------------+-------------------------------+---------------+----------+------------------------------------+----------------------------+----------+------+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table               |          |      | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy   | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence            |          | v1.0 | 2.0.5
 1351333217527-1 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:42:39.667878+09 |             3 | EXECUTED | 3:500d2782c8ddcc7d7b89b0b29b2d7342 | Create Table               |          |      | 2.0.5
 1351333217527-2 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:42:39.686335+09 |             4 | EXECUTED | 3:d4dcaaa9285f777d2a05f33e275488b7 | Add Column                 |          |      | 2.0.5
 1351333217527-3 | yamkazu (generated) | add-author.groovy | 2012-10-28 17:42:39.699466+09 |             5 | EXECUTED | 3:77e6c768bd70db135e30edfb2ef6788e | Add Foreign Key Constraint |          |      | 2.0.5

ここでv1.0の状態までロールバックしたいとする。まずはsqlから確認。

grails> dbm-rollback-sql v1.0
| Starting dbm-rollback-sql for database test @ jdbc:postgresql://localhost:5432/devDb
-- *********************************************************************
-- Rollback to 'v1.0' Script
-- *********************************************************************
-- Change Log: changelog.groovy
-- Ran at: 12/10/28 17:43
-- Against: test@jdbc:postgresql://localhost:5432/devDb
-- Liquibase version: 2.0.5
-- *********************************************************************

-- Lock Database
-- Rolling Back ChangeSet: add-author.groovy::1351333217527-3::yamkazu (generated)::(Checksum: 3:77e6c768bd70db135e30edfb2ef6788e)
ALTER TABLE book DROP CONSTRAINT FK2E3AE9B2F6003C;

DELETE FROM databasechangelog  WHERE ID='1351333217527-3' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-2::yamkazu (generated)::(Checksum: 3:d4dcaaa9285f777d2a05f33e275488b7)
ALTER TABLE book DROP COLUMN author_id;

DELETE FROM databasechangelog  WHERE ID='1351333217527-2' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

-- Rolling Back ChangeSet: add-author.groovy::1351333217527-1::yamkazu (generated)::(Checksum: 3:500d2782c8ddcc7d7b89b0b29b2d7342)
DROP TABLE author;

DELETE FROM databasechangelog  WHERE ID='1351333217527-1' AND AUTHOR='yamkazu (generated)' AND FILENAME='add-author.groovy';

| Finished dbm-rollback-sql

問題無さそうなので実行。

grails> dbm-rollback v1.0
| Finished dbm-rollback

DBの確認。

devDb=> \d
                 List of relations
 Schema |         Name          |   Type   | Owner 
--------+-----------------------+----------+-------
 public | book                  | table    | test
 public | databasechangelog     | table    | test
 public | databasechangeloglock | table    | test
 public | hibernate_sequence    | sequence | test
(4 rows)

devDb=> select * from databasechangelog;
       id        |       author        |    filename     |         dateexecuted          | orderexecuted | exectype |               md5sum               |   description   | comments | tag  | liquibase 
-----------------+---------------------+-----------------+-------------------------------+---------------+----------+------------------------------------+-----------------+----------+------+-----------
 1351331869483-1 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.890237+09 |             1 | EXECUTED | 3:378572087c807b0eae512c8e1bac00b7 | Create Table    |          |      | 2.0.5
 1351331869483-2 | yamkazu (generated) | add-book.groovy | 2012-10-27 19:07:44.907147+09 |             2 | EXECUTED | 3:1f8c97c0685b3c4f68b4ac2954ed9919 | Create Sequence |          | v1.0 | 2.0.5
(2 rows)

v1.0のタグまでロールバックしていることがわかる。

ロールバックはこんなもんで。