kagamihogeの日記

kagamihogeの日記です。

JEP 321: HTTP Client (Standard)をテキトーに訳した

http://openjdk.java.net/jeps/321 を読んだ。

JEP 321: HTTP Client (Standard)

Owner    Chris Hegarty
Created 2017/06/08 11:46
Updated 2018/01/31 16:29
Type    Feature
Status  Candidate
Component   core-libs/java.net
Scope   SE
Discussion  net dash dev at openjdk dot java dot net
Effort  M
Duration    M
Priority    2
Reviewed by Alan Bateman, Brian Goetz
Endorsed by Brian Goetz
Issue   8181784

Summary

JDK 9とJDK 10のupdatedで導入されたincubatedのHTTP Client APIを標準化します。これはJEP 110由来のものです。

Goals

JEP 110のGoalsに加え、このJEPでは以下を行います。

  • incubated APIに寄せられたフォードバックの取り込み
  • incubated APIをベースにjava.net.httpパッケージで標準APIを提供
  • incubated APIの削除

Motivation

このJEPのMotivationはJEP 110のMotivationから変更はありません。

Description

このJEPの提案内容は、JDK 9とJDK 10のupdatedでincubating APIとして導入されたHTTP Clientの標準化です。このincubating APIには重要な改善に繋がった多数のフィードバックが寄せられましたが、大筋はそのままです。このAPIにはノンブロッキングリクエストとCompletableFuturesに基づくレスポンスセマンティクスが含まれ、依存アクションをトリガするチェーン処理ができます。リクエストとレスポンスボディのBack-pressureとflow-controlはjava.util.concurrent.Flow APIreactive-streamsを介して提供されます。

JDK 9とJDK 10ではincubatingなので、その実装をほぼ完全に書き直します。現在の実装は完全に非同期です(以前のHTTP/1.1実装はブロッキング)。RX Flowの概念を実装にプッシュダウンしており、HTTP/2をサポートするのに必要だった元々の概念の多くを削除しました。データフローのトレース、ユーザレベルのリクエストパブリッシャとレスポンスサブスクライバから基底のソケットまで、が簡単になります。これらにより、各種の概念とコードの複雑さを大きく削減し、HTTP/1.1とHTTP/2の再利用の可能性(possibility of reuse)を最大化しました。

提案した標準のモジュール名はjava.net.httpで、標準パッケージ名はjava.net.httpです。

APIの詳細についてはJEP 110を参照してください。

Testing

incubated APIに対する既存のテストを修正して新しい標準APIで使えるようにする必要があります。すべてのシナリオをカバーするテストを追加する必要があり、特にHTTP/1.1とHTTP/2間のアップグレード・ダウングレードが挙げられます。

Risks and Assumptions

incubated HTTP Client APIに依存するコードは、パッケージインポートを少々、修正の必要があります。その他のincubatedの機能に差異はありません。incubatingモジュールに依存するコードは既にコンパイル・実行時に適切な警告を出しています。

JEP 325: Switch Expressionsをテキトーに訳した

http://openjdk.java.net/jeps/325 をテキトーに訳した。

JEP 325: Switch Expressions

Author   Brian Goetz
Owner   Jan Lahoda
Created 2017/12/04 08:56
Updated 2018/01/29 16:23
Type    Feature
Status  Candidate
Component   specification/language
Scope   SE
Discussion  amber dash dev at openjdk dot java dot net
Effort  M
Duration    M
Priority    3
Reviewed by Alex Buckley
Issue   8192963

Summary

switchを文でも式としても使えるよう拡張し、また、nullの扱い方を改良します。この変更は今までの書き方を単純化するもので、switchパターンマッチング(JEP 305)に備える目的もあります。

Motivation

我々はJava言語にパターンマッチング(JEP 305)を導入するための拡張の準備をしていますが、既存のswitchの微妙ないくつかの点が障害となっており、これはユーザを長年イライラさせ続けているものでもあります。これにはnullの処理(switchは引数がnullだとNullPointerExceptionをスロー)と、swtichが文でしか使えない、があります。場合にもよりますが、式として複数条件分岐*1を表現する方が自然な場合があります。

既存のswitch文の多くの使われ方は本質的にはswitch式のシミュレーションで、各arm*2は共用のターゲット変数に代入するか値を返すかのどちらかをします。

int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalStateException("Wat: " + day);
};

文としてこれを表現するのは、似たようなことの繰り返しになるので、回りくどく間違いやすいです。上記コードの意図は日にちに対応するnumLettersの値を計算をする、というものです。これは、より直感的で、明瞭・安全に書けるのが望ましいです。

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
};

switch式への拡張は更に別の要求へと繋がっています。単純なORパターン形式(上の例で見たもの)、フローアナリシスの拡張(式は常に値を計算するか即時終了する*3)、switch式の各caseで値の算出ではなく例外をスロー可能にする、などです。

Description

式として使えるようにswitch文を拡張します。よくある例としては、switch式は以下のようになります。

T result = switch (arg) {
    case L1 -> e1;
    case L2 -> e2;
    default -> e3;
}

switch式は複合的な式(poly expression)になります。ターゲット型が既知であれば、その型は各armにプッシュダウンします。既知であればswitch式の型はそのターゲット型となり、そうでない場合、各caseの型との組み合わせでスタンドアローンの型が算出されます*4

switch文では、breakがswitchの実行を停止させます。switchで使うために、breakが引数を取れるよう拡張し、その値はswitch式の値になります。

switch式のcaseはbreakを介して式で値を算出しつつ文の実行も出来ます。その場合switch文のように複数の文を書きます。

int result = switch (s) {
    case "Foo":
        break 1;
    case "Bar":
        break 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

ただし、上記のような例は稀で、文は無く単一の式を評価する複数のcase、を想定しています。また、シンタックスシュガーを提供するので、その場合はswitch式は以下のように定義します。

case LABEL -> expression;

元はこうです。

case LABEL: break expression;

よって、上記例は簡潔に書けます。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

また、switch式では以下のようにも書けます。

case LABEL -> throw e;

元はこうです。

case LABEL: throw e;

よって、以下のようになります。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default -> throw new IllegalStateException(s);
}

これらの書き方は必要に応じて使い分けられるので、上記のswitch式は以下のようにも書けます。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

breakの2つの形式(値有・無)はメソッドのreturnの2つの形式と似ています。両者ともメソッド実行を即時停止し、非voidメソッドでは加えて、メソッドの呼び出し元に値を返す必要があります。(break expression-valuebreak labelのあいまいさは比較的簡単に処理可能です)

switch式のcaseは網羅的である必要があり、とりうる入力に対しarmのうち一つだけが評価される必要があります。これは基本的にはdefault句が必要という意味ですが、enumのswitch式で取りうる値をすべて網羅する場合(and eventually, switch expressions over sealed types)、default句はコンパイラが挿入して、コンパイラenum定義がコンパイル時と実行時で変わったことを示します。(これは現状開発者が手でやっていますが、コンパイラによる挿入は面倒を省いてくれて、エラーメッセージは手で書いたよりも記述的となります)

更にswitchの改良を推し進めて、switchの式と文をある程度同じにします。case句で一つしか書けないのをカンマ区切りのリストで書けるようにします。

switch (day) {
    case MONDAY, FRIDAY, SUNDAY: 
        numLetters = 6;
        break;
    ...
};

引数が参照型(現状ではプリミティブのラッパー・文字列・enumのみ)のswitchでは、case句は明示的にnullを指定できます。

String formatted = switch (s) {
    case null -> "(null)";
    case "" -> "(empty)";
    default -> s;
}

もしswitchcase null句が無い場合、最初のcase句は以下であると解釈されます。

case null: throw new NullPointerException();

この挙動はswitchで参照型を用いる際の現行の振る舞いと一致します。

なおもし余裕があれば、switchで現行では使えないプリミティブ型、float, double, longなど、を使えるようにする拡張もするかもしれません。

Dependencies

Pattern Matching (JEP 305)はこのJEPに依存します。

*1:multi-way conditionalsが原文。訳は俺が勝手につけたもの。言うまでもなくswitch-caseのような複数の条件にマッチするヤツのこと

*2:switchからcaseが伸びる様をarmと表現してると思われ

*3:compute a value or complete abruptlyが原文。良い日本語思い浮かばず

*4:この辺良くわからん。

FlywayのdocumentationのConceptsを読んだ

https://flywaydb.org/documentation/ を読んだ。

Overview

Flywayはデータベースマイグレーションをやりやすくします。

Tip: すぐに読み終えられるのでGet Startedセクションを先に目を通すことをオススメします。

Flywayはオープンソースのデータベースマイグレーションツールです。単純さとCoCに特に主眼を置いています。

Flywayは7つの基本コマンド群から構成されています。Migrate, Clean, Info, Validate, Undo, Baseline and Repair.

マイグレーションSQL(データベース固有の文法(PL/SQL, T-SQL)もサポート)もしくはJava(複雑なデータ変換やLOBの処理など向け)で書きます。

Flywayにはコマンドラインクライアント](https://flywaydb.org/documentation/commandline)があります。JVM上で動かしてアプリケーション開始時にDBをマイグレーションするにはJava APIAndroid上でも動作)の使用を推奨します。もしくは、Maven pluginGradle pluginを使います。

上記で不足する場合、Spring Boot, Dropwizard, Grails, Play, SBT, Ant, Griffon, Grunt, Ninjaなどで使えるプラグインがあります。

サポート対象のDBは以下の通りです。Oracle, SQL Server(Amazon RDSとAzure SQL Database含む), DB2, MySQLAmazon RDS, Azure Database & Google Cloud SQL), MariaDB, PostgreSQLAmazon RDS, Azure Database, Google Cloud SQL & Heroku含む), Redshift, CockroachDB, SPAP HANA, Sybase ASE, H2, HSQLDB, Derby, SQLLite

Migrations

Overview

FlywaではDBに対するすべての変更をマイグレーション(migrations)と呼びます。マイグレーションはバージョン付き(versiond)かリピータブル(repeatable)の二種類あります。バージョン付きマイグレーションには二つの形式、通常(regular)と取消(undo)、があります。

バージョン付きマイグレーション(Versioned migrations)は、バージョン(version)と説明(description )とチェックサムchecksum)、で構成されます。バージョンは一意にする必要があります。説明は、個々のマイグレーションが何をしているかという、人間向けの単なる情報です。チェックサムは意図しない変更を検出するためのものです。バージョン付きマイグレーションマイグレーションの基本形です。マイグレーションは一度だけ順番に適用されます。

また、同一バージョンに取消マイグレーション(undo migration)を付けることでそのマイグレーションを取り消し可能にも出来ます。

リピータブルマイグレーション(Repeatable migrations)は説明とチェックサムを持ちますが、バージョンがありません。一度だけ実行するのではなく、代わりに毎回チェックサムの変更が(再)適用されます。

単一のマイグレーション実行では、リピータブルマイグレーションは、すべてのペンディング状態のバージョン付きマイグレーションが実行された後、常に最後に適用されます。リピータブルマイグレーションは説明の順に適用されます*1

デフォルトではバージョン付き・リピータブルマイグレーションのどちらもSQLJavaのどちらかで作成可能で、複数のステートメントを書けます。

FlywayはファイルシステムJavaのクラスパス上のマイグレーションを自動検出します。

どのマイグレーションがいつ誰によって適用されたかをトラッキングするために、Flywayはスキーマschema history tableを追加します。

Versioned Migrations

最もよく使うマイグレーションバージョン付きマイグレーションです。個々のバージョン付きマイグレーションは、バージョン・説明・チェックサム、を持ちます。バージョンは一意にする必要があります。説明は、個々のマイグレーションが何をしているかという、人間向けの単なる情報です。チェックサムは意図しない変更を検出するためのものです。バージョン付きマイグレーションマイグレーションの基本形です。マイグレーションは一度だけ順番に適用されます。

バージョン付きマイグレーションは基本的には以下の用途に使われます。

  • create/alter/drop table,index,foreign keys,enum,UDT
  • 参照データの更新(Reference data updates)
  • データ修正

以下は一例です。

CREATE TABLE car (
    id INT NOT NULL PRIMARY KEY,
    license_plate VARCHAR NOT NULL,
    color VARCHAR NOT NULL
);

ALTER TABLE owner ADD driver_license_id VARCHAR;

INSERT INTO brand (name) VALUES ('DeLorean');

バージョン付きマイグレーションにはそれぞれに一意のバージョンを割り当てる必要があります。一般的なドット記法に沿えば任意のバージョンが有効です。通常なら単純増加の整数で十分でしょう。ただし、Flywayは柔軟性があり以下のようなバージョン付きマイグレーションの番号が有効です。

  • 1
  • 001
  • 5.2
  • 1.2.3.4.5.6.7.8.9
  • 205.68
  • 20130115113556
  • 2013.1.15.11.35.56
  • 2013.01.15.11.35.56

バージョン付きマイグレーションはその番号順に適用されます。バージョンは数値としてソートされます。

Undo Migrations

Flyway Pro

取消マイグレーションは通常のバージョン付きマイグレーションの逆です。取消マイグレーションの役割は同一バージョンのマイグレーションの影響を取消すことにあります。取消マイグレーションは無くても良く、通常のバージョン付きマイグレーションの実行時には不要です。

上述の例を取ると、取消マイグレーションは以下のようになります。

DELETE FROM brand WHERE name='DeLorean';

ALTER TABLE owner DROP driver_license_id;

DROP TABLE car;

Important Notes

取消マイグレーションの考え方は魅力的に見えますが、実際には何らかを壊す場合があります。破壊的変更(drop, delete, truncate)はトラブルの元です。そうでないとしても、バックアップから復元するためのお手製のソリューションを十分にテストする羽目になります。

前提として、取消マイグレーションマイグレーション全体が成功している場合に取消を行うものです。そのためDDLトランザクション無しで失敗したバージョン付きマイグレーションをどうにかすることは出来ません。その理由は、マイグレーションがどの段階で失敗するかは分からないためです。いま10ステートメントあるとして、1,5,7,10番目が失敗するかもしれません。事前にこれを検知する方法はありません。これとは対照的に、取消マイグレーションはバージョン付きマイグレーション全体を取消します。よってそうした状況下をなんとかするものではありません。

我々が推奨する代替案はDBと現行の本番環境にデプロイされているコードの全バージョンとの間に後方互換性を持ち続けるというものです。この場合であればマイグレーションの失敗はトラブルになりません。アプリケーションの旧バージョンはDBと互換性が保たれているので、アプリケーションをロールバックし、調査して、しかるべき処置を行います。

この案は適切で、十分にテストした、バックアップとリストアによって実現します。特定のDBの構造に非依存で、テストして実証が取れたら、マイグレーションスクリプトは何も壊さなくなります。パフォーマンスの最適化には、使用しているインフラストラクチャがサポートしていれば、基底ストレージのスナップショット機能の使用を推奨します。特に大規模データボリュームでは、伝統的なバックアップとリストアよりも格段に高速となる場合があります。

Repeatable Migrations

リピータブルマイグレーションは説明とチェックサムを持ちますが、バージョンがありません。一度だけ実行する代わりに毎回チェックサムの変更が(再)適用されます。

定義を持つデータベースオブジェクトの管理に有用で、バージョン管理下の単一ファイルで扱えるようになります。基本的には以下のように使います。

  • view/procedure/function/packeageの(再)生成
  • バルクで参照データの再insert

単一のマイグレーション実行中では、ペンディング中のバージョン付きマイグレーションがすべて実行されたあと、リピータブルマイグレーションは常に最後に実行されます。リピータブルマイグレーションはその説明の順序で適用されます。

同一のリピータブルマイグレーションが複数回適用出来るように作成するのはプログラマの責任です。基本的にはDDLステートメントCREATE OR REPLACEを使います。

リピータブルマイグレーションの例は以下のようになります。

CREATE OR REPLACE VIEW blue_cars AS 
    SELECT id, license_plate FROM cars WHERE color='blue';

SQL-based migrations

マイグレーションは基本的にSQLで書きます。使い始めやすく、既存のスクリプト、ツールやスキルを流用できます。DBの全機能が使用可能で、中間の変換レイヤーを理解する手間を省けます。

SQLベースのマイグレーションは基本的には以下のように使います。

  • DDL(TABLE,VIEW,TRIGGER,SEQUENCEのCREATE/ALTER/DROPステートメント
  • 単純な参照データ変更(参照データテーブルのCRUD
  • 単純なバルクデータ変更(通常データテーブルのCRUD

Naming

Flywayにマイグレーションファイルを認識させるには、以下のネーミングルールに従う必要があります。

(ここにVersioned Migrations, Undo Migrations, Repeatable Migrationsの図がCSSを駆使して描かれている。本家ページ参照)

https://flywaydb.org/documentation/migrations#naming

ファイル名は以下のパーツで構成されます。

  • Prefix: バージョン付きの場合v変更), 取消の場合u変更)、リピータブルの場合R変更
  • Version: ドットもしくはアンダースコアでバージョンを記述(リピータブルでは不要)
  • Separator: __(アンダースコア2個)(変更
  • Description: 単語はアンダースコアかスペースで区切る。
  • Suffix: .sql変更

Discovery

FlywayはファイルシステムJavaクラスパス両方でSQLベースのマイグレーションを参照します。マイグレーションlocationsプロパティが指す一つ以上のディレクトリに置きます。

locationsにfilesystem:プレフィクスを付けるとファイルシステムをターゲットにします。プレフィクス無しかclasspath:を付けるとJavaのクラスパスをターゲットにします。

(ここにCSSを駆使した図解がある。下記URLの本家参照。)

https://flywaydb.org/documentation/migrations#discovery

新しいSQLベースのマイグレーションは実行時に自動的にファイルシステムJavaクラスパスのスキャンを行います。必要に応じてlocationsを設定する場合、Flywayは設定した命名規約にマッチする新規のSQLマイグレーションを自動的に検出します*2

スキャンは再帰的に行います。指定ディレクトリ下のすべてのディレクトリが対象となります。

Syntax

Flywayはすべての標準SQLをサポートします。これには以下を含みます。

  • 単一ハイフンもしくは複数行のステートメント
  • 単一ハイフン(en-dash)もしくは複数行(/* */)コメント
  • DB固有のSQLの文法拡張(PL/SQL, T-SQL)でストアドやパッケージの定義など

補足としてOracleの場合、FlywayはSQL*Plus commandsもサポートします。

Placeholder Replacement

標準SQLに加えて、Flywayはプレースホルダによる置換もサポートします。デフォルトでは${myplaceholder}のようなAntスタイルのプレールホルダになります。

環境間の差異を抽象化するのに役立ちます。

Example

サポートしているSQLの一例です。

/* Single line comment */
CREATE TABLE test_user (
  name VARCHAR(25) NOT NULL,
  PRIMARY KEY(name)
);

/*
Multi-line
comment
*/

-- Placeholder
INSERT INTO ${tableName} (name) VALUES ('Mr. T');

Java-based migrations

JavaベースのマイグレーションSQLでの表現が難しい変更をしたい場合に適しています。

基本的には以下のような場合に使います。

  • BLOB, CLOBの変更
  • 複雑なバルクデータの変更(再計算・複雑なフォーマット変更など)

Naming

Flywayに認識させるには、JavaベースのマイグレーションJdbcMigrationインタフェースを実装する必要があります。

デフォルトではFlywayはクラス名からバージョンと説明を自動抽出します。そのためには、クラス名は以下のネーミングルールに従う必要があります。

(ここにVersioned Migrations, Undo Migrations, Repeatable Migrationsの図がCSSを駆使して描かれている。本家ページ参照)

https://flywaydb.org/documentation/migrations#naming-1

ファイル名は以下のパーツで構成されます。

  • Prefix: バージョン付きの場合v, 取消の場合u、リピータブルの場合R
  • Version: アンダースコア(実行時に自動的にドットで置換される)で区切る。個数は任意(リピータブルでは不要)
  • Separator: __(アンダースコア2個)
  • Description: アンダースコア(実行時に自動的にスペースで置換される)で単語を区切る

クラス名にカスタマイズが必要な場合、MigrationInfoProviderインタフェースを実装してデフォルトの規約をオーバーライドします。

オーバーライドによりクラス名を任意に変更できます。バージョン・説明・マイグレーションカテゴリは対応するメソッドの実装で与えます。

Discovery

Flywayはlocationsプロパティが指すパッケージ内のJavaクラスパスにあるマイグレーションを参照します。

(ここにCSSを駆使した図解がある。下記URLの本家参照。)

https://flywaydb.org/documentation/migrations#discovery-1

新しいJavaベースのマイグレーションは実行時にクラスパスのスキャンで自動検出を行います。スキャンは再帰的に行います。あるパッケージのサブパッケージも対象になります。

Checksums and Validation

SQLマイグレーションとは異なり、Javaマイグレーションはデフォルトではチェックサムを持たないのでFlywayのvalidationによる変更検出の対象となりません。これはMigrationChecksumProviderインタフェースの実装により改良できます。getChecksum()メソッドで自前のチェックサムを実装します。これによりチェックサムが保存されてvalidationに使われます。

Sample Class

package db.migration;

import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * Example of a Java-based migration.
 */
public class V1_2__Another_user implements JdbcMigration {
    public void migrate(Connection connection) throws Exception {
        PreparedStatement statement =
            connection.prepareStatement("INSERT INTO test_user (name) VALUES ('Obelix')");

        try {
            statement.execute();
        } finally {
            statement.close();
        }
    }
}

Spring support (Optional)

Springの場合、SpringJdbcMigrationインタフェースを実装するという選択肢もあります。JdbcMigrationと同様な動作をしますが、素のJDBCではなくSpringのJdbcTemplateを使える点が異なります。

package db.migration;

import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * Example of a Spring Jdbc migration.
 */
public class V1_2__Another_user implements SpringJdbcMigration {
    public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
        jdbcTemplate.execute("INSERT INTO test_user (name) VALUES ('Obelix')");
    }
}

Transactions

デフォルトでは、Flywayは単一トランザクションマイグレーション実行を常にラップします。また、groupプロパティをtrueにすることで、単一トランザクション内にすべてのマイグレーション実行をラップできます*3

DBのなんらかの技術的制限が原因で、トランザクションで特定のステートメントを実行できないことをFlywayが検出する場合、トランザクションでそのマイグレーションは実行されません。その場合non-transactionalとマーキングされます。

デフォルトでは、トランザクショナルと非トランザクショナルのステートメントを単一マイグレーション実行に混ぜることは出来ません。ただし、mixedtrueにすることで可能になります。

Important Note

DBがcleanlyなトランザクションDDLステートメントをサポートする場合、失敗マイグレーションは常にロールバックします(ただしnon-transactionalのマーキングが無い場合に限る)。

一方、DBがcleanlyなトランザクションDDLステートメントをサポートしない場合(DDLステートメント前後に暗黙的なコミットを発行する場合など)、Flywayは失敗時にクリーン・ロールバックを実行出来ないので、マイグレーションを失敗とマーキングし、なんらかのマニュアルクリーンアップが必要なことを通知します。

Query Results

Flyway Pro

マイグレーションは第一義にリリースとデプロイ自動化プロセスの一部として実行されるので、SQLの結果を目で確認するケースは稀です。

とはいえ、そういう目視検査が意味を持つ場合も無くはないので、Flyway ProとEnterprise EditionではSELECT(と結果を返すその他のステートメント)実行時に表形式でクエリ結果を表示できます。

Schema History Table

どのマイグレーションがいつ誰によって適用されたかをトラッキングするために、Flywayはスキーマschema history tableを追加します。このテーブルはスキーマに対するすべての変更の実行記録と見なせます。また、マイグレーションチェックサムと個々のマイグレーションが成功したかどうかも持ちます。

使い方についてはhow Flyway worksのgetting started guideを読んで下さい。

Migration States

マイグレーションresolvedappliedのどちらかになります。ResolvedのマイグレーションはFlywayのファイルシステム・クラスパスのスキャナーが検出した 状態です。最初はpendingになります。DBに対して一度でも実行されると、appliedになります。

マイグレーションが成功するとFlywayのschema history tablesuccessになります。

マイグレーションが失敗し、かつ、DBがDDLトランザクションをサポートする場合、ロールバックしてschema history tableには何も記録されません。

マイグレーションが失敗し、かつ、DBがDDLトランザクションをサポートしない場合、schema history tableはfailedとなり、なんらかのマニュアルクリーンアップが必要かもしれないことを通知します。

取消マイグレーションでバージョン付きマイグレーションが取消された場合はundoneになります。

リピータブルマイグレーションが最後に適用された後にチェックサムが変更されている場合はoutdatedになり、再度実行するまでこのままになります。

既知の最も高いバージョンよりも高いappliedのバージョン付きマイグレーション(これは基本的にはソフトウェアの新バージョンが対象スキーマをマイグレートした場合に起きる)をFlywayが参照すると、そのマイグレーションfutureになります。

Callbacks

マイグレーションはおおよそのニーズを満たしますが、同一のアクションを何度も実行したい場合があります。プロシージャの再コンパイル、マテリアライズドビューの更新、様々なハウスキープ処理など。

そのため、Flywayはコールバックによりライフサイクルにフックをかけられます。

以下がFlywayがサポートするフックです。

Name Execution
beforeMigrate マイグレーション実行前
beforeEachMigrate マイグレーション実行中の各マイグレーション実行前
afterEachMigrate マイグレーション実行中の各マイグレーション実行後
afterMigrate マイグレーション実行後
beforeUndo Flyway Pro 取消マイグレーション実行前
beforeEachUndo Flyway Pro 取消マイグレーション中の各マイグレーション実行前
afterEachUndo Flyway Pro 取消マイグレーション中の各マイグレーション実行後
afterUndo Flyway Pro 取消マイグレーション実行後
beforeClean Clean実行前
afterClean Clean実行後
beforeInfo Info実行前
afterInfo Info実行後
beforeValidate Validate実行前
afterValidate Validate実行後
beforeBaseline Baseline実行前
afterBaseline Baseline実行後
beforeRepair Repair実行前
afterRepair Repair実行後

コールバックはSQLJavaのどちらかで実装します。

SQL Callbacks

Flywayのライフサイクルにフックをかける最も簡単な方法はSQLコールバックです。これは設定した場所に命名規約に従ってSQLファイルを単に置くもので、コールバック名の後ろにSQLマイグレーションサフィックス(※デフォルト.sql)を付けます。

デフォルト設定では、Flywayはデフォルトロケーション(CLIでは<install_dir>/sql)のbeforeMigrate.sql, beforeEachMigrate.sql, afterEachMigrate.sqlなどを参照します。

プレースホルダの置換はSQL migrationsと同様です。

Note: なおFlywayはSQLコールバックスキャンの際にsqlMigrationSuffixesの設定を優先します。

Java Callbacks

SQLコールバックが合わない場合、FlywayCallbackインターフェースを実装することもできます。ライフサイクルに複数のコールバック実装をフックさせることもできます。

More info: Java-based Callbacks

Error Handlers

Flyway Pro

FlywayのSQL実行はDBが返すすべての警告をレポートします。エラーが返される場合Flywayはすべての必要となる詳細を表示し、マイグレーションを失敗にして、可能であればロールバックを自動的に行います。

エラーは基本的に以下のように表示されます。

Migration V1__Create_person_table.sql failed
--------------------------------------------
SQL State  : 42001
Error Code : 42001
Message    : Syntax error in SQL statement "CREATE TABLE1[*] PERSON "; expected "OR, FORCE, VIEW, ...
Location   : V1__Create_person_table.sql (/flyway-tutorial/V1__Create_person_table.sql)
Line       : 1
Statement  : create table1 PERSON

デフォルトの振る舞いで基本的には充分です。

ただし、以下のようなことをしたい場合があります。

  • エラーが既知なので警告として扱い、後でしかるべき処置をしたい。
  • 警告をフェイルファーストにしたいのでエラーとして扱い、すぐに問題解決できるようにしたい。
  • DBが特定のエラーか警告を出す場合に特定の処理を紐づけたい。

Flyway ProとEnterprise Editionにはこれらを実現するためのエラーハンドラ(Error Handlers)があります。

Implementation

エラーハンドラはorg.flywaydb.core.api.errorhandler.ErrorHandlerインタフェースを実装するJavaクラスです。

このインターフェースにはメソッドが一つ(boolean handle(Context context))あり、DBが警告かエラーを少なくとも一つ返す場合に呼ばれます。インタフェース実装ではエラーや警告を見てそれに応じた処理をします。メッセージのログ出力や、例外のスローが出来ます。trueはエラーと警告が処理されたことを示します。falseはFlywayに次に設定されているErrorHandlerに進ませることを指示し、もし無い場合はデフォルトの振る舞いにフォールバックします。

Configuration

flyway.errorHandlersプロパティに複数のエラーハンドラを設定します。エラーハンドラは設定した順に呼び出されます。一つも設定されていない場合はFlywayはデフォルトの振る舞いにフォールバックします。

Example

Orcleのストアドプロシージャのコンパイルエラーは、デフォルトでは、ドライバは単に警告を出してFlywayはそれを出力します。

DB: Warning: execution completed with warning (SQL State: 99999 - Error Code: 17110)

以下は警告をエラーとして扱うエラーハンドラの例です。

package org.mycompany.mypkg;

import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.errorhandler.Context;
import org.flywaydb.core.api.errorhandler.ErrorHandler;
import org.flywaydb.core.api.errorhandler.Warning;

public class OracleProcedureFailFastErrorHandler implements ErrorHandler {
    @Override
    public boolean handle(Context context) {
        for (Warning warning : context.getWarnings()) {
            if ("99999".equals(warning.getState()) && warning.getCode() == 17110) {
                throw new FlywayException("Compilation failed");
            }
        }
        return false;
    }
}

Flywayには以下のように設定し、

flyway.errorHandlers=org.mycompany.mypkg.OracleProcedureFailFastErrorHandler

OrcleのストアドプロシージャのコンパイルエラーはCompilation failed即時エラー(immediate error)になります。

Dry Runs

Flyway Pro

FlywayがDBにマイグレーションを実行するときの動きは、適用対象のマイグレーションを参照し、ソートしてDBに対して直接それらを適用します。

デフォルトの振る舞いで基本的には充分です。

ただし、以下のようなことをしたい場合があります。

  • FlywayがDBにする予定の変更のプレビュー
  • 適用前にDBAにSQLのレビューを依頼
  • Flywayで更新されるものを確認し、実際のDB変更は別のツールでやる

Flyway ProとEnterprise Editionにはこれらを実現するためのDry Runsがあります。

Implementation

Dry Runの場合、Flywayはread-onlyでDB接続をセットアップします。Flywayはマイグレーション実行に必要なものを調べ、通常のマイグレーションで実行予定のすべてのステートメントを含む単一のSQLファイルを生成します。このSQLファイルをレビューします。これで十分な場合、FlywayにDBへのマイグレートを指示し、そうするとすべての変更が適用されます。もしくは、Flywayを使わず別のツールでDBに直接dry runのSQLファイルを適用することも出来ます。なお、このSQLファイルにはFlywayのschema history tableの生成と更新に必要なステートメントが含まれており、すべてのスキーマ変更は他同様にトラッキングされます。

この動作は透過的なので、その他のFlywayの機能である、SQLJavaマイグレーション、バージョン付き・リピータブル、コールバック、取消マイグレーションなどと一緒に使えます。

Configuration

Flywayのcommand-line tool, Maven plugin, Gradle pluginで使う場合、dry runの出力となるSQLファイルはflyway.dryRunOutputプロパティで設定します。

直接APIを使う場合、dry runの出力はjava.io.OutputStreamで設定します。CLIより細かい制御が出来ます。

プロパティを設定するとFlywayのdry runモードが有効になります。DBは変更されなくなり、適用予定のすべてのSQLステートメントがdry runの出力に送られるようになります。

*1:Repeatable migrations are applied in the order of their description.が原文。実際に試してないんだがdescriptionが順序になるだと思われ

*2:Flyway will automatically pick up any new SQL migrations as long as they conform to the configured naming convention.が原文。newてことはold(があるかどうかも分からんが)は自動検出対象外なんすかね?

*3:... the entire execution of all migrations of a single migration run within a single transaction ...が原文なんだが、なんか文章がヘンなような…… https://flywaydb.org/documentation/commandline/migrate#group を見ると「同一トランザクションにすべてのペンディング中のマイグレーションをグループ化する(DDLトランザクションをサポートするDBでのみ推奨)」と書いてある