トリガの使用法
この章では、Caché SQL でトリガを定義する方法を説明します。トリガは、特定の SQL イベントに応答して実行されるコード行です。以下の項目について説明します。
トリガの定義
特定のテーブルにトリガを定義するには、以下のような方法があります。
-
スタジオを使用して、テーブルに対応するクラス定義に SQL トリガ定義を追加します。例えば、MyApp.Person クラスのこの定義には LogEvent トリガの定義が含まれます。これは、INSERT を呼び出すたびに起動されます。
Class MyApp.Person Extends %Persistent [DdlAllowed] { // ... Definitions of other class members /// This trigger updates the LogTable after every insert Trigger LogEvent [ Event = INSERT, Time = AFTER ] { // get row id of inserted row NEW id SET id = {ID} // INSERT value into Log table &sql(INSERT INTO LogTable (TableName, IDValue) VALUES ('MyApp.Person', :id)) } // ... Definitions of other class members }
-
DDL CREATE TRIGGER コマンドを使用して、トリガを作成します。
この章では、Caché SQL トリガについて説明します。Caché MultiValue トリガは、Caché SQL トリガとはまったく別のものです。SQL を更新しても MultiValue トリガは起動せず、MultiValue を更新しても SQL トリガは起動しません。
1 つのクラスのユーザ定義トリガの最大数は 200 です。
Caché SQL は、コレクションによって投影されたテーブルのトリガをサポートしていません。ユーザはこのようなトリガを定義することはできず、子テーブルとしてのコレクションの投影では、その基本コレクションに関わるトリガは考慮されません。
トリガのタイプ
トリガは、その実行の要因となるイベントのタイプ (トリガの Event キーワード) と、トリガをイベントの前に実行するか後で実行するかの指定 (トリガの Time キーワード) で定義します。同じイベントおよび同じタイミングで実行するトリガが複数存在する場合は、それらのトリガを実行する順序を定義できます (トリガの Order キーワード)。
トリガの Foreach キーワードはより詳細な精度を提供します。このキーワードは、トリガが行ごとに 1 回発生するか、行またはオブジェクト・アクセスごとに 1 回 発生するか、または文ごとに 1 回発生するかを制御します。詳細は、"Caché クラス定義リファレンス" を参照してください。Foreach = row/object を使用してトリガが定義されている場合、このドキュメントの後半で説明するように、トリガはオブジェクト・アクセスの特定の時点でも呼び出されます。
トリガには、シングルイベント・トリガとマルチイベント・トリガがあります。シングルイベント・トリガは、指定したテーブルで INSERT、UPDATE、または DELETE のいずれかのイベントが発生したときに実行するように定義します。マルチイベント・トリガは、指定したテーブルで、指定したイベントのいずれか 1 つが発生したときに実行するように定義します。マルチイベント・トリガとして、INSERT/UPDATE、UPDATE/DELETE、または INSERT/UPDATE/DELETE を定義できます。
以下は、使用できるトリガです。
-
BEFORE INSERT (%OnBeforeSave コールバックと同じ機能を提供します。つまり、%OnBeforeSave と同じです。)
-
AFTER INSERT (%OnAfterSave と同じです。)
-
BEFORE UPDATE (%OnBeforeSave と同じです。)
-
AFTER UPDATE (%OnAfterSave と同じです。)
-
指定された列の BEFORE UPDATE OF
-
指定された列の AFTER UPDATE OF
-
BEFORE DELETE (%OnDelete と同じです。)
-
AFTER DELETE
1 つの Event や Time に複数のトリガを定義できます。この場合、トリガの Order キーワードにより、複数のトリガが動作する順番を管理できます。Order 値が低いトリガから順番に動作します。複数のトリガが同じ Order 値を持つ場合は、動作する順番は不定になります。
トリガが実行されるときに、処理されるテーブル内のプロパティの値をその実行で直接変更することはできません。これは、Caché が、フィールド (プロパティ) 値検証コードの後のトリガ・コードを実行するからです。例えば、処理中の行にある現在のタイムスタンプに LastModified フィールドを設定できません。ただし、トリガ・コードはテーブル内のフィールド値に対して UPDATE を発行できます。UPDATE は、独自のフィールド値検証を実行します。
詳細は、"Caché SQL リファレンス" の "CREATE TRIGGER" を参照してください。
AFTER トリガ
AFTER トリガは、INSERT、UPDATE、または DELETE イベントの発生後に実行されます。
-
SQLCODE=0 の場合 (イベントが正常に完了した場合)、Caché は AFTER トリガを実行します。
-
SQLCODE が負の数値の場合 (イベントが失敗した場合)、Caché は AFTER トリガを実行しません。
-
SQLCODE=100 の場合 (挿入、更新または削除する行が見つからなかった場合)、Caché は AFTER トリガを実行します。
再帰トリガ
Caché は、AFTER トリガの再帰的な実行を防止します。例えば、テーブル T1 には、テーブル T2 への挿入を実行するトリガがあり、テーブル T2 には、テーブル T1 への挿入を実行するトリガがあるとします。Caché は、実行スタック内で AFTER トリガが以前に呼び出されていることを検出すると、AFTER トリガを発行しません。エラーは発行されません。トリガは 2 回目には実行されなくなるだけです。
Caché は、BEFORE トリガの再帰的な実行を防止しません。BEFORE トリガの再帰の処理は、プログラマの責任になります。BEFORE トリガのコードで再帰的な実行を処理していないと、実行時 <FRAMESTACK> エラーが発生する可能性があります。
トリガ・コードの機能
各トリガには、トリガされたアクションを実行する 1 行以上のコードが含まれます。このコードは、トリガが定義されたイベントが発生した場合、常に SQL エンジンによって呼び出されます。トリガが CREATE TRIGGER を使用して定義されている場合、このアクション・コードを ObjectScript または SQL で記述できます (Caché は、SQL で記述されたコードをクラス定義の ObjectScript に変換します)。スタジオを使用して定義したトリガの場合、このアクション・コードは ObjectScript で記述する必要があります。
トリガ・コード内で、特別な {field_name} 構文を使用して、(トリガが関連付けられているテーブルに属するフィールドの) フィールド値を参照できます。例えば、MyApp.Person クラスの LogEvent トリガの以下の定義には、ID フィールドへの参照が {ID} として含まれます。
Class MyApp.Person Extends %Persistent [DdlAllowed]
{
// ... Definitions of other class members
/// This trigger updates the LogTable after every insert
Trigger LogEvent [ Event = INSERT, Time = AFTER ]
{
// get row id of inserted row
NEW id
SET id = {ID}
// INSERT value into Log table
&sql(INSERT INTO LogTable
(TableName, IDValue)
VALUES ('MyApp.Person', :id))
}
// ... Definitions of other class members
}
トリガ・コードが正常に終了すると、%ok=1 に設定されます。トリガ・コードが失敗すると、%ok=0 に設定されます。INSERT または UPDATE トリガ・コードが失敗し、テーブルに外部キー制約が定義されている場合は、Caché によって、外部キー・テーブル内の対応する行のロックが解除されます。
トリガのコードはプロシージャとして生成されないため、トリガ内のすべてのローカル変数はパブリック変数となります。つまり、トリガ内のすべての変数は NEW 文で明示的に宣言される必要があります。これにより、トリガを呼び出すコード内の変数との競合を避けることができます。
%ok 変数を 0 に設定することで、トリガ・コードからエラーを発行できます。この設定によって、実行時エラーが生成され、トリガの実行が中止されます。また、トリガ・コードでは %msg 変数に実行時エラーの起きた節を記述する文字列を設定することもできます。
トリガ・コードは、変数 %oper も参照できます。この変数には、トリガを発生させるイベントの名前 (INSERT、UPDATE、または DELETE) が含まれます。
トリガ・コード内のマクロ
トリガ・コードには、フィールド名を参照するマクロ定義を含めることができます ({field_name} 構文を使用)。ただし、トリガ・コードに、フィールド名を参照する ({field_name} 構文を使用) マクロの #Include プリプロセッサ指示文が含まれている場合、そのフィールド名にはアクセスできません。これは、Caché がトリガ・コード内の {field_name} 参照を、そのコードがマクロ・プリプロセッサに渡される前に変換するためです。{field_name} 参照が #Include ファイル内にある場合、それはトリガ・コード内では “表示“ されないため、変換されません。
この状況を回避するには、引数を指定してマクロを定義してから、トリガ内のマクロに {field_name} を渡します。例えば、#Include ファイルに、以下のような行を含めることができます。
#Define dtThrowTrigger(%val) SET x=$GET(%val,"?")
次に、{field_name} 構文を引数として指定して、トリガ内でマクロを呼び出します。
$$$dtThrowTrigger({%%ID})
{name*O}、{name*N}、および {name*C} トリガ・コード構文
UPDATE トリガ・コードでは 3 種類の構文ショートカットを使用できます。
以下の構文を使用して古い (更新前の) 値を参照できます。
{fieldname*O}
ここで、fieldname はフィールドの名前で、アスタリスクの後ろの文字はアルファベットの “O” です (Old を意味しています)。INSERT トリガの場合は、{fieldname*O} は必ず空文字列 ("") となります。
以下の構文を使用して新しい (更新後の) 値を参照できます。
{fieldname*N}
ここで、fieldname はフィールドの名前で、アスタリスクの後ろの文字はアルファベットの “N” です (New を意味しています)。この {fieldname*N} 構文は、格納される値を参照するためにのみ使用できます。値を変更するためには使用できません。トリガ・コードに {fieldname*N} を設定することはできません。INSERT または UPDATE でのフィールド値の計算は、SqlComputeOnChange などの他の方法で実行する必要があります。
以下の構文を使用して、フィールド値が変更 (更新) されたかどうかをテストできます。
{fieldname*C}
ここで、fieldname はフィールドの名前で、アスタリスクの後ろの文字はアルファベットの “C” です (Changed を意味しています)。{fieldname*C} は、フィールドが変更されているときは 1、変更されていないときは 0 となります。INSERT トリガの場合は、Caché によって {fieldname*C} が 1 に設定されます。
ストリーム・プロパティがあるクラスでは、SQL 文 (INSERT または UPDATE) がストリーム・プロパティ自体を挿入または更新しなかった場合は、ストリーム・プロパティ {Stream*N} および {Stream*O} への SQL トリガ参照では、ストリームの OID が返されます。ただし、SQL 文がストリーム・プロパティを挿入または更新した場合は、{Stream*O} は OID のままですが、{Stream*N} 値は以下のいずれかに設定されます。
-
BEFORE トリガは、UPDATE または INSERT に渡された形式を問わず、ストリーム・フィールドの値を返します。これは、ストリーム・プロパティに入力されたリテラル・データ値か、一時ストリーム・オブジェクトの oref または oid となります。
-
AFTER トリガ゙は、ストリームの ID を {Stream*N} 値として返します。これは、Caché によって、そのストリーム・フィールドのためにグローバルな ^classnameD 内に格納される ID 値です。この値は、そのストリーム・プロパティの CLASSNAME 型パラメータに基づく適切な ID 形式となります。
ストリーム・プロパティが、Caché オブジェクトを使用して更新される場合は、{Stream*N} 値は必ず oid となります。
シリアル・オブジェクトの配列コレクションによって作成された子テーブルのトリガの場合、トリガ・ロジックはオブジェクト・アクセス/保存で機能しますが、SQL アクセス (INSERT または UPDATE) では機能しません。
その他のトリガ・コード構文
ObjectScript で記述するトリガ・コードには、擬似フィールド参照変数の {%%CLASSNAME}、{%%CLASSNAMEQ}、{%%OPERATION}、{%%TABLENAME}、および {%%ID} を含めることができます。これらの擬似フィールドは、クラスのコンパイル時に特定の値に変換されます。詳細は、"Caché SQL リファレンス" の "CREATE TRIGGER" を参照してください。
クラス・メソッドは開いているオブジェクトの有無に依存しないため、クラス・メソッドをトリガ・コード、SQL 計算コード、および SQL マップ定義内から使用できます。トリガ・コード内からクラス・メソッドを呼び出すには、##class(classname).Methodname() 構文を使用する必要があります。..Methodname() 構文では、現在開いているオブジェクトが必要なので、この構文は使用できません。
クラス・メソッドの引数として現在の行のフィールドの値を渡すことができますが、クラス・メソッド自体はフィールド構文を使用できません。
トリガとオブジェクト・アクセス
Foreach = row/object を使用してトリガが定義されている場合、トリガ定義の Event および Time キーワードに応じて、トリガは以下のようにオブジェクト・アクセスの特定の時点でも呼び出されます。
Event | Time | トリガが呼び出される時点 |
---|---|---|
INSERT | BEFORE | 新しいオブジェクトの %Save() の直前 |
INSERT | AFTER | 新しいオブジェクトの %Save() の直後 |
UPDATE | BEFORE | 既存のオブジェクトの %Save() の直前 |
UPDATE | AFTER | 既存のオブジェクトの %Save() の直後 |
DELETE | BEFORE | 既存のオブジェクトの %DeleteId() の直前 |
DELETE | AFTER | 既存のオブジェクトの %DeleteId() の直後 |
この結果、SQL とオブジェクトの動作の同期を維持するためにコールバック・メソッドを実装する必要もなくなります。
Foreach トリガのキーワードの詳細は、"Caché クラス定義リファレンス" を参照してください。
オブジェクト・アクセス時にトリガをプルしない
%Storage.SQL を使用するクラスでオブジェクトを保存または削除する際、文レベル、行レベル、および行/オブジェクト・レベルのトリガがすべてプルされます。これが既定の動作です。
ただし、%Storage.SQL を使用するクラスでオブジェクトを保存または削除する際、行/オブジェクト・レベルのトリガのみがプルされるように指定できます。文レベルのトリガと行レベルのトリガはプルされません。このためには、クラス・パラメータ OBJECTSPULLTRIGGERS = 0 を指定します。既定は OBJECTSPULLTRIGGERS = 1 です。
このパラメータは、%Storage.SQL を使用して作成されたクラスにのみ適用されます。
トリガとトランザクション
トリガは、トランザクションの範囲内でトリガ・コードを実行します。トリガは、トランザクション・レベルを設定してから、トリガ・コードを実行します。トリガ・コードが正常に完了すると、トリガによってトランザクションがコミットされます。
トランザクションを使用したトリガの場合、トランザクションをコミットするコードをトリガで呼び出すと、トランザクション・レベルがすでに 0 にデクリメントされているためにトリガの完了が失敗します。この状況は、Ensemble ビジネス・サービスを呼び出したときに発生することがあります。
AFTER INSERT 文レベルの ObjectScript トリガの使用では、トリガを %ok=0 にした場合、行の挿入は SQLCODE -131 エラーにより失敗します。以下のように、トランザクションのロールバックが発生することがあります。
-
AUTO_COMMIT=ON の場合、INSERT に対するトランザクションがロールバックされます。
-
AUTO_COMMIT=OFF の場合、INSERT に対するトランザクションのロールバックまたはコミットはアプリケーションによって決まります。
-
NO_AUTO_COMMIT モードが使用された場合、トランザクションは開始されないので、INSERT はロールバックできません。
AUTO_COMMIT モードは SET TRANSACTION %COMMITMODE オプションまたは $SYSTEM.SQL.SetAutoCommit()Opens in a new tab メソッドを使用して確立されます。
このトリガにより、トリガ内の %msg 変数にエラー・メッセージが設定されます。このメッセージは発信者宛てに返され、トリガの失敗理由についての情報が提供されます。
%ok および %msg のシステム変数は、このドキュメントの “埋め込み SQL の使用法” の章の システム変数 のセクションで説明しています。
トリガのリスト
INFORMATION.SCHEMA.TRIGGERSOpens in a new tab クラスを使用して、現在定義されているトリガをリストできます。このクラスは各トリガについて、トリガ名、関連するスキーマとテーブル名、およびトリガ作成タイムスタンプをリストします。さまざまなプロパティがトリガごとにリストされます。これには、EVENTMANIPULATION プロパティ (INSERT、UPDATE、DELETE、INSERT/UPDATE、INSERT/UPDATE/DELETE)、ACTIONTIMING プロパティ (BEFORE、AFTER)、および生成された SQL トリガ・コードの ACTIONSTATEMENT プロパティが含まれます。