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

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コマンドなどを使用してください。