最終チェック (TakeFinally)

処理概要

DBFluteプロパティ databaseInfoMap.dfprop に定義されたスキーマに接続し、データ登録の処理が終わった後に 仕様に合致するファイルに定義されているSQL文を実行し、最終チェックを行います。

SQLファイルの実行

以下の項目以外については、仕様は "テーブル作成" と同じ仕様です。

SQLファイル名の違い
SQLファイル名の始まりは "replace-schema" ではなく "take-finally" となります。
実行タイミングの違い
データ登録が終わった後に実行されます。
データの整合性チェック
実際に登録されたデータを検索して、不整合なデータが存在しないかのチェックができます。
その他違い
  • システムユーザでの実行機能なし
  • 外部スクリプトを実行できる

データ登録後に実行したいSQL

ここでの処理は、"登録されたデータのデータの整合性チェック" がメインの目的ですが、その他にも以下のような "データ登録後に実行したいSQL" にも利用できます。

  • SQL関数でパスワードを暗号化する(データファイル上はplainで定義しておいて)
  • Oracleで "purge recyclebin" を実行する
  • Oracleで "マテリアライズドビュー" を作成する

データの整合性チェック

不整合なデータを検出する "select文" を定義して、検出された場合に処理が中断するようなチェックをすることができます。

ここでいう "不整合なデータ" とは、主には "どうしてもDBMSの制約(FKやUQなど)ではチェックできない業務的な制約を違反したデータ" のことを示します。

不整合なデータのリストを検出

select文に "-- #df:assertListZero#" というSQLコメント(assertコメント)を定義すると、その検索が一件でも結果を持った場合にReplaceSchemaが中断され、検出されたデータがログに出力されます。"select句" には、検出した場合に(人間が)データを特定できるためのカラムを列挙しておきます。

e.g. 最終チェックのアサート @take-finally.sql
-- #df:assertListZero#
-- 退会会員が退会情報を持っていることをアサート
select member.MEMBER_ID, member.MEMBER_NAME
  from MEMBER member
 where member.MEMBER_STATUS_CODE = 'WDL'
   and not exists (select withdrawal.MEMBER_ID
                     from MEMBER_WITHDRAWAL withdrawal
                    where withdrawal.MEMBER_ID = member.MEMBER_ID
       )
;

-- #df:assertListZero#
-- 会員住所情報において、同じ会員で重複した期間がないことをアサート
select adr.MEMBER_ADDRESS_ID, adr.MEMBER_ID
     , adr.VALID_BEGIN_DATE, adr.VALID_END_DATE
     , adr.ADDRESS
  from MEMBER_ADDRESS adr
 where exists (select subadr.MEMBER_ADDRESS_ID
                 from MEMBER_ADDRESS subadr
                where subadr.MEMBER_ID = adr.MEMBER_ID
                  and subadr.VALID_BEGIN_DATE > adr.VALID_BEGIN_DATE
                  and subadr.VALID_BEGIN_DATE < adr.VALID_END_DATE
       )
;

不整合なデータのカウントを検出

また、"select count(*)文" では "-- #df:assertCountZero" が利用できます。但し、この場合は検出時に不整合なデータのカウント(件数)しかわからないので、通常は "assertListZero" の方がお奨めです。

e.g. 正式会員なのに正式会員日時がないデータをチェック @take-finally.sql
-- #df:assertCountZero#
select count(*)
  from MEMBER
 where MEMBER_STATUS_CODE = 'FML'
   and FORMALIZED_DATETIME is null

存在するべきデータのチェック

不整合なデータを検出するでなく、"こういったデータが存在すること" というチェックもできます。 定義するSQLコメントの "Zero" の部分を "Exists" に変更することでそのような挙動になります("List"と "Count" 両方サポート)。

e.g. 会員ステータスは一件以上あることをチェック @take-finally.sql
-- #df:assertListExists#
select *
  from MEMBER_STATUS
e.g. 会員ステータスは一件以上あることをチェック @take-finally.sql
-- #df:assertCountExists#
select count(*)
  from MEMBER_STATUS

この場合は、エラー発生時の情報として "Count" と "List" で特に差はありませんので、SQLを書く上で都合の良い方を選びます。

テストパターンのチェック

データの整合性チェックは、"業務的な整合性チェック" だけでなく、"テストパターンのチェック" にも利用できます。例えば、"結合テスト環境(の初期状態)において、正式会員が一人もいないのはNG" という場合に、"結合テスト環境のみでチェック" という指定が可能です。

SQL一つ一つごとの切り替え

"assertListExists" の後ろに "@[repsEnvType]" を付与すると("Count" や "Zero" でも利用可能)、実行時の "repsEnvType" と一致するときだけチェックされるようになります。

e.g. 正式会員が一人もいないのはNG(結合テスト環境:"it") @take-finally.sql
-- #df:assertListExists@it#
select *
  from MEMBER
 where MEMBER_STATUS_CODE = 'FML'

また、複数指定することで、環境によってチェックの仕方を切り替えることも可能です。

e.g. 会員はUT環境では存在して、本番環境(の初期状態)では一人もいないこと(本番環境:"real") @take-finally.sql
-- #df:assertCountExists@ut#
-- #df:assertCountZero@real#
select count(*) from MEMBER member

この切り替え機能は、特に "テストパターンのチェック" に特化しているわけではなく、業務的な制約のチェックにも利用出来ますが、利用ケースとして考えたときに、"テストパターンのチェック" 専用の機能と言っても過言ではありません。(業務的な制約のチェックは、基本的にどの環境でも共通な制約であると考えられるため)

また、"テストパターンのチェック" を行う場合は、必ずこの "repsEnvType" を指定することをお奨めします。最初はUT環境だけで利用していても、後から結合環境でも利用するということはよくあるため、 少なくとも "UT環境のみ" における "テストパターンのチェック" かどうかは意識して定義することをお奨めします。

SQLファイルごとの切り替え

"環境ごとの実行チェック" 機能を使ってSQLファイルごとに切り替えることもできます。

二種類のチェックを明確に

"業務的な整合性チェック" と "テストパターンのチェック" のSQLが一つのファイルの中でごっちゃにならないようにすることをお奨めします。 例えば、SQLコメントで定義する位置を統一するとか、それぞれ専用のファイルに分けて管理するとか、工夫次第で簡単に明確に管理できます。 また、そうすることで、SQLを定義する人が "どっちのチェックを書いているのか" を明確にすることができます。 (但し、グレーゾーンが存在することもあるので、一概に明確にできないものもありますので、臨機にファイルを分けて管理するようにして下さい)

e.g. 二種類のチェックをファイルを分けて管理 @playsql
playsql
 |-take-finally-10-basic.sql   // チェック以外のSQL 
 |-take-finally-20-constraint.sql   // 業務的な整合性チェック 
 |-take-finally-30-testpattern.sql  // テストパターンのチェック
 |-take-finally-40-othercheck.sql   // グレーゾーンなチェック 

また、実際の開発運用として、両方しっかり定義するのか、片方だけに集中するのかを明確にするのも大事です。例えば、"業務的な整合性チェック" だけを定義するなら、そのことをSQLを定義する人(できれば、プロジェクトに関わる全ての人に)にしっかり周知しておくことをお奨めします。

テストデータの品質向上

DBMSによる制約チェックには限度があります。どうしても業務的な制約(人間しか知らない)が発生してしまいがちです。 そして、ディベロッパーは開発時にデバッグ作業に追われるのが常ですが、その多くの原因が "不正なテストデータ" であることも珍しくありません。また、多くのディベロッパーが "プログラムが悪いのかデータが悪いのか" の切り分けに多くの時間を割いてしまいます。そして、これらは予期せぬ膨張工数として、 マネジメントする側としてもかなりやっかいな工数となります(最初からわかっている工数なら幾らでも調整のしようがありますが...)。

ReplaceSchemaでテストデータを管理し、そして、この整合性チェックSQLを整備すればするほど、そのリスクは減ります。 データを登録する時点でそういった "予期せぬ工数を生み出しかねない不整合なデータ" を除去しようという発想です。 テストデータの品質を向上させ、ディベロッパーが余計な切り分け作業に時間を割くようなことがないようにします。

もちろん、書き過ぎるとチェックSQLの管理が膨大になってしまうので、 特徴的で間違いやすく重要なものだけをチョイスしてチェックしていくのが現実的でしょう。 業務的one-to-oneは構造として非常に特徴的であるため不正なデータが入るとデバッグ等で面倒なことが多く想定されるため、必ずチェックしたいところです。

TakeFinallyで外部スクリプト

TakeFinally において、SQLの実行だけでなく外部スクリプトを実行することができます。 大量データ登録など、DBMSのコマンドを利用して特殊な操作をすることもあるでしょう。 そういう場合のために、外部スクリプトが実行できるようになっています。@since 1.1.7

TakeFinally のSQLファイルと同じディレクトリに、take-finally で始まる拡張子が .bat もしくは .sh のスクリプトファイルを配置します。SQLファイルも含めてファイル名の昇順で実行されます。

e.g. TakeFinally の外部スクリプトの配置 @playsql
dbflute_maihamadb
 |-playsql
    |-take-finally-10-sea.sql
    |-take-finally-20-land.sql
    |-take-finally-30-piari.sh

スクリプトの exitCode が 0 以外の場合は、スクリプト実行エラーとみなされます。

スクリプトのコンソールログは、固定で UTF-8 として取得され、DBFluteタスクのログと一緒に出力されます。なので場合によっては、ログが文字化けする可能性はありますが、処理に影響はありません。