トリガー - 繰り返し変更可能オプション
著者まえがき: この記事の元の記事は2013年11月に掲載されたものです。ここで紹介するのは、データ モダナイゼーションの手法として常に私のお気に入りだった手法です。この手法を西暦2000年問題の頃に思い付いていたらよかったのにと思います。記事の内容は、フリーフォームRPG用に、また、2013年以降にRPGに導入された、いくつかのコーディングの機能強化に合わせるべく、アップデートを行っています。
ここのところ、モダナイゼーション プロジェクトの際に、私はbeforeトリガーとともにALWREPCHG(Allow Repeated Change、繰り返し変更可能)オプションを利用してきました。ALWREPCHGは、beforeトリガーが、挿入または更新されているレコードに対して変更を加えることを可能にし、実に強力なデータベース マジックを見せてくれます。
シナリオ
表には、パック数値列に格納されている日付があります。要件は、列のデータ型を適切な日付データ型に変更することです。その変更により、20以上のプログラムに対してコーディングの変更が必要になります。
一挙に20以上のプログラムを変更、テスト、実装する、「ビッグバン」アプローチを採るという選択肢はありません。プログラムの変更は、数か月にわたってその場その場で行われます。
求められるのは、両方のおいしいところ取りです。すなわち、新たな機能を実装しつつ、その間も確実にすべてが機能し続けるようにすることです。
ソリューション
以下の処理を行います。
- 表で、パック数値列を日付列として複製する。
- すべての行で、パック数値列の内容を日付列へ複製する。
- 表にアクセスするすべてプログラムを再コンパイルする。
- 表に挿入および更新のためにbeforeトリガーを追加する。このトリガーにより、パック数値列の内容と日付列との同期が取られていることが保証されます。
これで、任意のプログラムに対して、(新たな日付列を使用するために)必要とされる変更を加えることができるようになり、プログラムを本番稼働したときに、トリガー プログラムにより日付の同期が保たれます。
最後に、すべてのプログラムが更新されたら、以下を行うことができます。
- トリガーを削除する。
- パック数値データ列を削除する。
- 表にアクセスするすべてのプログラムを再コンパイルする。
これで、モダナイゼーション プロセスは完了です。
コード
Product表で、Date Last Sold(前回販売日)のパック数値列の名前はDATESOLDであり、新たな対応する日付列はLAST_SELLです。
以下のコードは、コピー メンバーTRIGPARMの内容を示しています。これには、すべてのトリガー プログラムに共通のパラメーター リスト、トリガー バッファー、および名前付き定数の定義が含まれています。主に注目すべきなのは、トリガー バッファーのevent、oldOffset、およびnewOffsetフィールドです。
**free
dcl-Pr trigger extPgm('TRIGGER');
triggerData likeDS(base_Trigger);
triggerDataLength int(10);
end-Pr;
dcl-Pi trigger;
triggerData likeDS(base_Trigger);
triggerDataLength int(10);
end-Pi;
dcl-Ds base_Trigger qualified template;
tableName char(10);
schemaName char(10);
memberName char(10);
event char(1);
time char(1);
commitLock char(1);
*n char(3);
CCSID int(10);
RRN int(10);
*n int(10);
oldOffset int(10);
oldLength int(10);
oldNullOffset int(10);
oldNullLength int(10);
newOffset int(10);
newLength int(10);
newNullOffset int(10);
newNullLength int(10);
end-Ds;
dcl-C EVENT_INSERT '1';
dcl-C EVENT_DELETE '2';
dcl-C EVENT_UPDATE '3';
dcl-C EVENT_READ '4';
dcl-C TIME_AFTER '1';
dcl-C TIME_BEFORE '2';
dcl-C COMMIT_NONE '0';
dcl-C COMMIT_CHG '1';
dcl-C COMMIT_CS '2';
dcl-C COMMIT_ALL '3';
dcl-C COLUMN_NULL '1';
dcl-C COLUMN_NOT_NULL '0';
ここで、トリガー プログラムTRIGPGMのコードを見てみましょう。トリガーが挿入の前に呼び出され、数値フィールドがゼロでない場合、またはトリガー プログラムが更新の前に呼び出され、数値列の内容が変わっている場合、このプロセスは数値列の日付を日付列に変換します。それ以外の場合は、日付列の内容が数値列に変換されます。
newRowデータ構造はポインターをベースにしているため、newRow.last_SellまたはnewRow.datesoldフィールドの値の変更は、トリガー バッファーの内容が変更されることを意味することに注意してください。また、変換されたプログラムでは、新たな行を挿入するときに必ず数値列内の日付がゼロに設定されるようにする必要があります。挿入では、数値列のゼロ値によって、確実に日付列が使用されることになります。
**free
/include qInclude,stdHSpec
/include qInclude,trigParm
dcl-Ds oldRow extName('PRODUCT') based(oldRowPtr) qualified;
end-Ds;
dcl-Ds newRow extName('PRODUCT') based(newRowPtr) qualified;
end-Ds;
oldRowPtr = %addr(triggerData) + triggerData.oldOffSet;
newRowPtr = %addr(triggerData) + triggerData.newOffSet;
if (triggerData.event = EVENT_INSERT);
if (newRow.dateSold <> 0);
newRow.last_Sell = %date(newRow.dateSold :*ISO);
else;
newRow.dateSold = %int(%char(newrow.last_sell :*ISO0));
endIf;
elseIf (triggerData.event = EVENT_UPDATE);
if (newRow.dateSold <> oldRow.dateSold);
newRow.last_Sell = %date(newRow.dateSold :*ISO);
elseIf (newRow.last_Sell <> oldRow.last_Sell);
newRow.dateSold = %int(%char(newrow.last_sell :*ISO0));
endIf;
endIf;
return;
トリガー プログラムを導入するために、以下のコマンドを使用して、2つのトリガー定義を追加します。
ADDPFTRG FILE(PRODUCT) TRGTIME(*BEFORE) TRGEVENT(*INSERT)
PGM(TRIGPGM2) TRG(TRG_PRODUCT_INSERT)
ALWREPCHG(*YES)
ADDPFTRG FILE(PRODUCT) TRGTIME(*BEFORE) TRGEVENT(*UPDATE)
PGM(TRIGPGM2) TRG(TRG_PRODUCT_UPDATE)
ALWREPCHG(*YES)
ALWREPCHG(*YES)パラメーターがなければ、トリガー プログラムでのnewRow.last_SellおよびnewRow.datesoldフィールドに対する変更は有効になりません。
最終的な留意事項
トリガー プログラムがトリガー バッファーを変更できるようにすることは(トリガーが追加されるときにパラメーター設定が必要だとしても)、セキュリティ上、重要な熟慮事項であると考える人も多いでしょう。では、どのトリガーがトリガー バッファーを変更しているのかは、どのようにしたらわかるのでしょうか。
PRTTRGPGM(Print Trigger Programs、トリガー プログラム印刷)コマンドによって生成されるレポートには、Allow Repeated Changeの列がありますが、Allow Repeated Changeがyesに設定されているトリガーのリストを得るのであれば、SQL selectステートメントを使用するほうが簡単です。
select * from qsys2/systrigger
where allow_repeated_change = 'YES'
このステートメントはシステム カタログにアクセスして、Allow Repeated Changeがyesに設定されているすべてのトリガーをリストします。