18 連続問合せ通知(CQN)の使用
連続問合せ通知(CQN)を使用すると、問合せをオブジェクト変更通知用(デフォルト)または問合せ結果変更通知用としてアプリケーションからデータベースに登録できます。登録済の問合せで参照されるオブジェクトは、登録済オブジェクトです。
オブジェクト変更通知(OCN)に対する問合せが登録されると、その問合せで参照されるオブジェクトがトランザクションによって変更されてコミットされるたびに、問合せ結果が変更されたかどうかにかかわらず、データベースからアプリケーションに通知されます。
問合せ結果変更通知(QRCN)に対して問合せが登録されると、その問合せの結果がトランザクションによって変更されてコミットされるたびに、データベースからアプリケーションに通知されます。
CQN登録により、1つ以上の問合せのリストが通知タイプ(OCNまたはQRCN)および通知ハンドラに関連付けられます。CQN登録を作成するには、PL/SQLインタフェースまたはOracle Call Interface(OCI)を使用できます。PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側のPL/SQLストアド・プロシージャになり、OCIを使用する場合、通知ハンドラはクライアント側のCコールバック・プロシージャになります。
この章では、CQNの一般的な概念と、PL/SQL CQNインタフェースの使用方法について説明します。
トピック:
ノート:
OCNおよびQRCNという用語は、通知タイプと通知自体の両方を指します。アプリケーションによりOCNに対する問合せが登録されると、データベースからアプリケーションにOCNが送信され、アプリケーションによりQRCNに対する問合せが登録されると、データベースからアプリケーションにQRCNが送信されます。
関連項目:
CQNのためのOCIの使用の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください。
18.1 オブジェクト変更通知(OCN)について
アプリケーションでオブジェクト変更通知(OCN)に対する問合せが登録されると、その問合せに関連付けられているオブジェクトがトランザクションによって変更されてコミットされるたびに、問合せ結果が変更されたかどうかにかかわらず、データベースからアプリケーションにOCNが送信されます。
たとえば、アプリケーションで例18-1のOCNに対する問合せが登録され、EMPLOYEES
表を変更するトランザクションがユーザーによってコミットされると、変更された行が問合せの条件句を満たしていない場合でも(DEPARTMENT_ID
= 5の場合など)、データベースからアプリケーションにOCNが送信されます。
例18-1 変更通知として登録される問合せ
SELECT EMPLOYEE_ID, SALARY FROM EMPLOYEES WHERE DEPARTMENT_ID = 10;
18.2 問合せ結果変更通知(QRCN)について
ノート:
QRCNを使用するには、データベースのCOMPATIBLE
初期化パラメータが11.0.0以上であり、自動UNDO管理(AUM)が有効(デフォルト設定)である必要があります。
アプリケーションで問合せ結果変更通知(QRCN)に対する問合せが登録されると、その問合せの結果がトランザクションによって変更されてコミットされるたびに、データベースからアプリケーションにQRCNが送信されます。
たとえば、アプリケーションで例18-1のQRCNに対する問合せが登録されると、問合せ結果セットが変更された場合のみ、つまり、次のデータ操作言語(DML)文のいずれかがコミットされた場合にのみ、データベースからアプリケーションにQRCNが送信されます。
-
問合せの条件句(
DEPARTMENT_ID
= 10)を満たす行のINSERT
またはDELETE
。 -
問合せの条件句(
DEPARTMENT_ID
= 10)を満たしている行のEMPLOYEE_ID
またはSALARY
列へのUPDATE
。 -
行の値が10から10以外の値に変更され、その行が結果セットから削除される原因となった
DEPARTMENT_ID
列へのUPDATE
。 -
行の値が10以外の値から10に変更され、その行が結果セットに追加される原因となった
DEPARTMENT_ID
列へのUPDATE
。
デフォルトの通知タイプはOCNです。QRCNの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_QUERY
を指定します。
QRCNでは、保証モード(デフォルト)またはベストエフォート・モードを選択できます。
トピック:
関連項目:
-
COMPATIBLE
初期化パラメータの詳細は、『Oracle Database管理者ガイド』を参照してください。 -
AUMの詳細は、『Oracle Database管理者ガイド』を参照してください。
18.2.1 保証モード
保証モードでは、誤検出はありません。つまり、問合せ結果セットの変更が保証される場合にのみ、データベースからアプリケーションにQRCNが送信されます。
たとえば、アプリケーションで例18-1のQRCNに対する問合せが登録され、従業員201が部門10に所属しており、次の文が実行されるとします。
UPDATE EMPLOYEES SET SALARY = SALARY + 10 WHERE EMPLOYEE_ID = 201; UPDATE EMPLOYEES SET SALARY = SALARY - 10 WHERE EMPLOYEE_ID = 201; COMMIT;
このトランザクションの各UPDATE
文によって問合せ結果セットは変更されますが、各文を合計すると問合せ結果セットへの影響はないため、このトランザクションに対してはデータベースからアプリケーションにQRCNは送信されません。
保証モードでは、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
ではなくQOS_QUERY
を指定します。
問合せの中には、保証モードのQRCNに対して複雑すぎるものがあります。
関連項目:
保証モードで登録可能な問合せの特性は、「保証モードでQRCNに対して登録可能な問合せ」を参照してください。
18.2.2 ベストエフォート・モード
保証モードでは複雑すぎる問合せの中には、ベストエフォート・モードでQRCNに対して登録できるものがあります。このモードでは、簡略バージョンの問合せがCQNによって作成および登録されます。
次の2つの例で、この動作を示します。
18.2.2.1 例: 保証モードのQRCNに対して複雑すぎる問合せ
例18-2の問合せは、集計関数SUM
を含んでおり、保証モードのQRCNに対しては複雑すぎます。
例18-2 保証モードのQRCNに対して複雑すぎる問合せ
SELECT SUM(SALARY)
FROM EMPLOYEES
WHERE DEPARTMENT_ID = 20;
ベストエフォート・モードでは、この例の問合せの次のような簡略バージョンがCQNによって登録されます。
SELECT SALARY
FROM EMPLOYEES
WHERE DEPARTMENT_ID = 20;
元の問合せの結果が変更されるたびに、その簡略バージョンの結果も変更されるため、簡略化が原因で通知が失われることはありません。ただし、簡略バージョンの結果は元の問合せの結果が変更されなくても変更される場合があるため、簡略化によって誤検出が発生することがあります。
ベストエフォート・モードでは、データベースは次の処理を行います。
-
通知関連の処理によるOLTPレスポンスのオーバーヘッドを次のように最小化します。
-
単一表問合せの場合、データベースでは、どの列が変更されたか、および変更された行によってどの条件句が満たされたかに基づいて、問合せ結果が変更されたかどうかを判断します。
-
複数表問合せ(結合)の場合、データベースでは、複数の表の間の主キー/外部キー制約関係を使用して、問合せ結果が変更されたかどうかを判断します。
-
-
DML文によって問合せ結果セットが変更されるたびに、最初のDML文によって行われた変更が後続のDML文によって無効になった場合でも、アプリケーションにQRCNを送信します。
ベストエフォート・モードでのオーバーヘッドを最小化により、CQNで簡略化されない問合せに対しても、誤検出がまれに発生します。たとえば、この例の問合せと、「保証モード」のトランザクションを想定します。ベストエフォート・モードでは、CQNによって問合せは簡略化されませんが、トランザクションによって誤検出が発生します。
18.2.2.2 例: 簡略バージョンによってオブジェクトが無効になる問合せ
一部のタイプの問合せは非常に簡略化されているため、無効化がオブジェクト・レベルで発生します。つまり、該当の問合せで参照されるいずれかのオブジェクトが変更されるたびに発生します。このような問合せの例としては、サポートされていない列型を使用する問合せや、副問合せを含んでいる問合せがあります。この問題を解決するには、元の問合せを書き換えます。
たとえば、例18-3の問合せは、副問合せを含んでいるため、保証モードのQRCNに対しては複雑すぎます。
例18-3 簡略バージョンによってオブジェクトが無効になる問合せ
SELECT SALARY FROM EMPLOYEES WHERE DEPARTMENT_ID IN ( SELECT DEPARTMENT_ID FROM DEPARTMENTS WHERE LOCATION_ID = 1700 );
ベストエフォート・モードでは、この例の問合せがCQNによって次のように簡略化されます。
SELECT * FROM EMPLOYEES, DEPARTMENTS;
問合せの簡略化により、オブジェクトが無効になる場合があります。ただし、元の問合せを次のように書き換えると、その問合せを保証モードまたはベストエフォート・モードで登録できます。
SELECT SALARY FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID AND DEPARTMENTS.LOCATION_ID = 1700;
ベストエフォート・モードでのみ登録可能な問合せについては、「ベストエフォート・モードでのみQRCNに対して登録可能な問合せ」で説明します。
デフォルトのQRCNモードは保証モードです。ベストエフォート・モードの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
を指定します。
18.3 通知を生成するイベント
通知を生成するイベントは次のとおりです。
18.3.1 DMLトランザクションのコミット
通知タイプがOCNの場合、1つ以上の登録済オブジェクトを変更するあらゆるDMLトランザクションで、コミット時に各オブジェクトに対して通知が1つ生成されます。
通知タイプがQRCNの場合、1つ以上の登録済問合せの結果を変更するあらゆるDMLトランザクションで、コミット時に通知が生成されます。この通知には、結果が変更された問合せのIDが含まれます。
どちらの通知タイプでも、通知には次の内容が含まれます。
-
変更された各表の名前
-
操作タイプ(
INSERT
、UPDATE
またはDELETE
) -
変更された各行の
ROWID
(ROWID
オプションを使用して登録が作成され、変更された行の数があまり多くない場合)。
関連項目:
18.3.2 DDL文のコミット
OCNとQRCNの両方について、次のデータ定義言語(DDL)文により、コミット時に通知が生成されます。
-
ALTER
TABLE
-
TRUNCATE
TABLE
-
FLASHBACK
TABLE
-
DROP
TABLE
ノート:
通知タイプがOCNの場合、DROP
TABLE
文のコミットによってDROP
NOTIFICATION
が生成されます。
削除された表に対する問合せのOCN登録は、その表(すでに存在しない表)から関連付けが解除されますが、登録自体は存続します。これらの登録のいずれかが削除された表以外のオブジェクトに関連付けられている場合、それらのオブジェクトに対する変更がコミットされると、通知は引き続き生成されます。削除された表にのみ関連付けられていた登録も存続し、その作成者は問合せ(およびその参照オブジェクト)をその登録に追加できます。
OCN登録は、問合せの登録時点におけるオブジェクトのバージョンと定義に基づきます。オブジェクトが削除されると、そのオブジェクトの登録の関連付けはオブジェクトから永続的に解除されます。オブジェクトが削除されたオブジェクトと同じ名前および同じスキーマで作成された場合、作成されたオブジェクトは削除されたオブジェクトに関連付けられていたOCN登録には関連付けられません。
通知タイプがQRCNの場合、次のようになります。
-
通知には次の内容が含まれます。
-
結果が変更された問合せのID
-
変更された表の名前
-
DDL操作のタイプ
-
-
登録済問合せを無効にするDDL操作の中には、その問合せを登録解除する原因となるものもあります。
たとえば、次の問合せがQRCNに対して登録されるとします。
SELECT COL1 FROM TEST_TABLE WHERE COL2 = 1;
TEST_TABLE
のスキーマは次のとおりとします。(COL1 NUMBER, COL2 NUMBER, COL3 NUMBER)
この場合、次のDDL文により、コミット時に問合せが無効になり、問合せが登録から削除されます。
ALTER TABLE DROP COLUMN COL2;
18.3.3 登録解除
OCNとQRCNの両方について、登録解除(データベースからの登録の削除)によって通知が生成されます。データベースで登録が削除される原因は次のとおりです。
-
タイムアウト
問合せの登録時に
TIMEOUT
がゼロ以外の値で指定された場合、指定の時間間隔後にデータベースによって登録がパージされます。問合せの登録時に
QOS_DEREG_NFY
が指定された場合、最初の通知の生成後にデータベースによって登録がパージされます。 -
権限の消失
登録済問合せに関連付けられているオブジェクトに対する権限が消失し、通知タイプがOCNの場合、データベースによって登録がパージされます。(通知タイプがQRCNの場合は、データベースによってその問合せは登録から削除されますが、登録はパージされません。)
クライアント・アプリケーションによる明示的な登録解除の実行時には、通知は生成されません。
関連項目:
問合せの登録に必要な権限は、「CQN登録を作成するための前提条件」を参照してください。18.3.4 グローバル・イベント
グローバル・イベントEVENT_STARTUP
およびEVENT_SHUTDOWN
により、通知が生成されます。
Oracle RAC環境では、次のイベントによって通知が生成されます。
-
EVENT_STARTUP
(データベースの最初のインスタンスの起動時) -
EVENT_SHUTDOWN
(データベースの最終インスタンスの停止時) -
EVENT_SHUTDOWN_ANY
(データベースのいずれかのインスタンスの停止時)
前述のグローバル・イベントは、DBMS_CQ_NOTIFICATION
パッケージで定義されている定数です。
関連項目:
DBMS_CQ_NOTIFICATION
パッケージの詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。
18.4 通知内容
通知には、次の情報の一部またはすべてが含まれます。
-
イベントのタイプ(次のいずれか)
-
起動
-
オブジェクト変更
-
問合せ結果変更
-
登録解除
-
停止
-
-
影響を受けた登録の登録ID
-
変更されたオブジェクトの名前
-
ROWID
オプションが指定されている場合の、変更された行のROWID
-
通知タイプがQRCNの場合の、結果が変更された問合せの問合せID
-
通知がDMLまたはDDL文の結果として生成された場合
-
変更された表の名前の配列
-
操作タイプ(
INSERT
、UPDATE
など)
-
通知には、変更されたデータ自体は含まれません。たとえば、5000から6000への月給の増額があっても、通知には示されません。変更されたオブジェクト、行または問合せ結果の最新の値を取得するには、アプリケーションからデータベースに問い合せる必要があります。
18.5 CQNに適したアプリケーション
CQNに適したアプリケーションは、データベースへのネットワーク・ラウンドトリップを回避するために、変更の頻度の低いオブジェクトに対する問合せの結果セットを中間層にキャッシュするアプリケーションです。これらのアプリケーションでは、CQNを使用して、キャッシュされる問合せを登録できます。このようなアプリケーションは、通知を受信すると、登録済問合せを再実行してキャッシュをリフレッシュできます。
このようなアプリケーションの例としては、Webフォーラムがあります。ユーザーは、コンテンツがデータベースに挿入されても、挿入後すぐにそのコンテンツを参照する必要はないため、このアプリケーションでは情報を中間層にキャッシュし、キャッシュをいつリフレッシュするかをCQNで指示できます。
図18-1に、データがデータベースから提供され、中間にキャッシュされた後、インターネット経由でアクセスされる一般的な使用例を示します。
中間層のアプリケーションは、キャッシュをデータベースと相対でできるかぎり最新の状態に保ちつつ、データベース・オブジェクトのキャッシュ・コピーに迅速にアクセスする必要があります。キャッシュ・データは、トランザクションでデータが変更されてコミットされると不要になるため、アプリケーションが不正な結果にアクセスする危険性があります。アプリケーションがCQNを使用している場合、データベースは、登録済オブジェクトに変更が発生した時点で変更内容の詳細を使用して通知を公開できます。この通知に応答して、アプリケーションではキャッシュ・データをバックエンド・データベースからフェッチしてリフレッシュできます。
図18-2に、中間層のWebクライアントが通知を受信して処理するプロセスを示します。
図18-2の各ステップは次のとおりです(PL/SQLを使用して登録が作成され、アプリケーションによりHR
.EMPLOYEES
に対する問合せの結果セットがキャッシュされている場合)。
-
開発者は、PL/SQLを使用して、問合せに対するCQN登録を作成します。このプロセスでは、通知を処理するストアドPL/SQLプロシージャを作成した後、PL/SQL CQNインタフェースを使用して問合せに対する登録を作成し、作成したPL/SQLプロシージャを通知ハンドラとして指定します。
-
データベースでは、データ辞書に登録情報が移入されます。
-
ユーザーがバックエンド・データベースの
HR
.EMPLOYEES
表の行を更新し、その更新をコミットすると、問合せ結果が変更されます。これで、中間層でキャッシュされていたHR
.EMPLOYEES
のデータが無効になります。 -
データベースにより、内部キューに変更を説明するメッセージが追加されます。
-
データベースから
JOBQ
バックグラウンド・プロセスに、通知メッセージが通知されます。 -
JOBQ
プロセスにより、クライアント・アプリケーションから指定されたストアド・プロシージャが実行されます。この例では、JOBQ
からサーバー側PL/SQLプロシージャにデータが渡されます。PL/SQL通知ハンドラの実装により、通知の処理方法が決まります。 -
開発者は、サーバー側PL/SQLプロシージャ内に、登録済オブジェクトに対する変更を中間層のクライアント・アプリケーションに通知するための論理を実装できます。たとえば、
HR
.EMPLOYEES
内で変更された行のROWID
をアプリケーションに通知します。 -
中間層のクライアント・アプリケーションでは、バックエンド・データベースを問い合せて、変更があった行のデータを取得します。
-
クライアント・アプリケーションにより、キャッシュがこのデータで更新されます。
18.6 CQN登録の作成
CQN登録により、1つ以上の問合せのリストが通知タイプおよび通知ハンドラに関連付けられます。
通知タイプは、OCNまたはQRCNです。
CQN登録を作成するには、2つのインタフェースのいずれかを使用します。
-
PL/SQLインタフェース
PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側PL/SQLストアド・プロシージャです。
-
Oracle Call Interface(OCI)
OCIを使用する場合、通知ハンドラはクライアント側Cコールバック・プロシージャです。
作成された登録は、データベースに格納されます。Oracle RAC環境では、この登録はすべてのデータベース・インタフェースから参照できます。いずれかのデータベース・インスタンスの問合せ結果がトランザクションによって変更されると、通知が生成されます。
デフォルトでは、登録は、それを作成したアプリケーションによって明示的に登録解除されるまで、または(権限の消失などが原因で)データベースによって暗黙的にパージされるまで存続します。
18.7 PL/SQLを使用したCQN登録の作成
この項では、PL/SQLを使用したCQN登録の作成について説明します。PL/SQLインタフェースを使用する場合、通知ハンドラはサーバー側PL/SQLストアド・プロシージャです。
トピック:
18.7.1 PL/SQL CQN登録インタフェース
PL/SQL CQN登録インタフェースは、DBMS_CQ_NOTIFICATION
パッケージを使用して実装されます。登録ブロックを開くには、DBMS_CQ_NOTIFICATION
.NEW_REG_START
ファンクションを使用します。通知タイプや通知ハンドラなどの登録詳細は、CQ_NOTIFICATION$_REG_INFO
オブジェクトの一部として指定し、このオブジェクトは引数としてNEW_REG_START
プロシージャに渡されます。登録ブロックがオープンの間に実行したすべての問合せは、CQNに登録されます。通知タイプとしてQRCNを指定すると、データベースによって各問合せに問合せIDが割り当てられます。この問合せIDは、DBMS_CQ_NOTIFICATION
.CQ_NOTIFICATION_QUERYID
ファンクションを使用して取得できます。登録ブロックをクローズするには、DBMS_CQ_NOTIFICATION
.REG_END
ファンクションを使用します。
関連項目:
-
DBMS_CQ_NOTIFICATION
パッケージの詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。
18.7.2 CQN登録オプション
CQN登録のデフォルトは、表18-1に示すオプションを使用して変更できます。
表18-1 連続問合せ通知登録オプション
オプション | 説明: |
---|---|
通知のタイプ |
QRCNを指定します(デフォルトはOCN)。 |
QRCNモード脚注1 |
ベストエフォート・モードを指定します(デフォルトは保証モード)。 |
|
変更された各行の |
操作フィルタ脚注2 |
指定したフィルタ条件を操作タイプが満たした場合のみ、通知を公開します。 |
トランザクション・ラグ脚注2 |
非推奨。かわりに通知グループ化を使用してください。 |
通知グループ化 |
通知のグループ化方法を指定します。 |
信頼性 |
通知を(デフォルトの共有メモリーではなく)永続データベース・キューに格納します。 |
通知時にパージ |
最初の通知後に登録をパージします。 |
タイムアウト |
指定の時間間隔後に登録をパージします。 |
脚注1
通知タイプがQRCNの場合にのみ適用されます。
脚注2
通知タイプがOCNの場合にのみ適用されます。
トピック:
18.7.2.2 QRCNモード(QRCN通知タイプのみ)
QRCNモード・オプションは、通知タイプがQRCNの場合にのみ適用されます。通知タイプをQRCNに設定する手順は、「「通知タイプ」オプション」を参照してください。
-
保証
-
ベスト・エフォート
デフォルトは保証モードです。ベストエフォート・モードの場合は、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_BEST_EFFORT
を指定します。
関連項目:
18.7.2.3 ROWIDオプション
ROWID
オプションにより、変更された行それぞれのROWID
疑似列の値(その行のrowid)が通知に含まれるようになります。変更された各行のROWID
オプションを通知に含めるには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_ROWIDS
を指定します。
ノート:
ハイブリッド列圧縮(HCC)で圧縮された表の行を更新すると、行のROWID
が変更されます。特定のOracleストレージ・システムの機能であるHCCの詳細は、『Oracle Database概要』を参照してください。
アプリケーションでは、次の形式の問合せを実行すると、変更された行の内容を通知のROWID
情報から取得できます。
SELECT * FROM table_name_from_notification WHERE ROWID = rowid_from_notification;
ROWID
は、外部文字列形式で公開されます。通常のヒープ表では、ROWID
の長さは18バイトです。索引構成表(IOT)では、ROWID
の長さは主キーのサイズによって異なり、18バイトより長い場合があります。
ROWID
に十分なメモリーがサーバーにない場合、通知はFULL-TABLE-NOTIFICATION
にロールアップされることがあります(これは、通知記述子内の特殊フラグで示されます)。FULL-TABLE-NOTIFICATION
に対して考えられる原因は次のとおりです。
-
ROWID
によって消費される共有メモリーの合計が動的共有プール・サイズの1%を超えています。 -
1回のトランザクションで1つの登録済オブジェクト内で変更された行が多すぎます(上限は約80行)。
-
IOTについて変更された行の論理
ROWID
の合計長が長すぎます(上限は約1800バイト)。 -
通知グループ化オプション
NTFN_GROUPING_TYPE
に値DBMS_CQ_NOTIFICATION
.NTFN_GROUPING_TYPE_SUMMARY
が指定されています(「通知グループ化オプション」を参照)。
FULL-TABLE-NOTIFICATION
にはROWID
は含まれないため、これを受信したアプリケーションでは表全体(つまり、すべての行)が変更された可能性があるとみなされます。
18.7.2.4 操作フィルタ・オプション(OCN通知タイプのみ)
操作フィルタ・オプションは、通知タイプがOCNの場合にのみ適用されます。
操作フィルタ・オプションを使用すると、通知を生成する操作のタイプを指定できます。
デフォルトは全操作です。一部の操作でのみ通知が生成されるように指定するには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのOPERATIONS_FILTER
属性を使用します。OPERATIONS_FILTER
属性では、操作のタイプを表す定数を使用してタイプを指定し、この定数は、DBMS_CQ_NOTIFICATION
パッケージで次のように定義されています。
演算子 | 定数 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
すべて(デフォルト) |
|
複数の操作を指定するには、ビット単位のOR
を使用します。たとえば:
DBMS_CQ_NOTIFICATION.INSERTOP + DBMS_CQ_NOTIFICATION.DELETEOP
QOS_QUERY
は通知タイプとしてQRCNを指定するため、QOSFLAGS
属性にQOS_QUERY
も指定した場合はOPERATIONS_FILTER
は無効です。
関連項目:
DBMS_CQ_NOTIFICATION
パッケージの詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。
18.7.2.5 トランザクション・ラグ・オプション(OCN通知タイプのみ)
トランザクション・ラグ・オプションは、通知タイプがOCNの場合にのみ適用されます。
ノート:
このオプションは非推奨です。フロー制御の通知を実装するには、「通知グループ化オプション」を使用してください。
トランザクション・ラグ・オプションでは、クライアント・アプリケーションがデータベースから遅れることのできるトランザクション数を指定します。数が0の場合、登録済オブジェクトを変更するトランザクションごとに通知が送信されます。数が5の場合、登録済オブジェクトを変更するトランザクションのうち5番目ごとに通知が送信されます。データベースでは、クライアントで中間の変更が失われないように、中間の変更をオブジェクトの粒度で追跡して通知に含めます。
0より大きいトランザクション・ラグが役立つのは、アプリケーションでフロー制御の通知を実装している場合のみです。アプリケーションでは、通知がラグに達する程度に十分な頻度で生成されるようにし、無限に遅延されないようにしてください。
TRANSACTION_LAG
を指定すると、QOS_ROWIDS
も指定した場合でも、通知にROWID
は含まれません。
18.7.2.6 通知グループ化オプション
デフォルトでは、通知はその原因となるイベントの直後に生成されます。
通知グループ化オプションは、CQ_NOTIFICATION$_REG_INFO
オブジェクトの属性であり、次の種類があります。
属性 | 説明: |
---|---|
|
通知をグループ化するクラスを指定します。使用可能な値は、 |
|
グループを定義する時間間隔を秒単位で指定します。たとえば、この値が900の場合、同じ15分間隔内に生成された通知がグループ化されます。 |
|
次のいずれかのグループ化のタイプを指定します。
|
|
通知生成の開始時間を指定します。 |
|
通知を繰り返す回数を指定します。登録の存続中に通知を永続的に受信するには、 |
ノート:
タイムアウト、権限の消失およびグローバル・イベントによって生成された通知は、指定したグループ化間隔が終了する前に公開される場合があります。この場合、グループ化された保留中の通知が存在すると、間隔が終了する前にその通知も公開されます。
18.7.2.7 信頼可能オプション
デフォルトでは、CQN登録は共有メモリーに格納されます。これをかわりに永続データベース・キューに格納するには(つまり、信頼できる通知を生成するには)、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_RELIABLE
を指定します。
信頼できる通知のメリットは、その生成後にデータベースで障害が発生しても、再起動後にその通知を配信できることです。Oracle RAC環境では、正常なデータベース・インスタンスがこの通知を配信できます。
信頼できる通知のデメリットは、デフォルトの通知よりもCPUおよびI/Oのコストが高いことです。
18.7.2.8 「通知時にパージ」および「タイムアウト」オプション
デフォルトでは、CQN登録は、それを作成したアプリケーションによって明示的に登録解除されるまで、または(権限の消失などが原因で)データベースによって暗黙的にパージされるまで存続します。
登録の最初の通知の生成後にその登録をパージするには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのQOSFLAGS
属性にQOS_DEREG_NFY
を指定します。
n秒後に登録をパージするには、CQ_NOTIFICATION$_REG_INFO
オブジェクトのTIMEOUT
属性にnを指定します。
「通知時にパージ」と「タイムアウト」の2つのオプションは、同時に使用できます。
18.7.3 CQN登録を作成するための前提条件
CQN登録を作成するための前提条件は次のとおりです。
-
次の権限が必要です。
-
DBMS_CQ_NOTIFICATION
パッケージのEXECUTE
権限(登録の作成にこのパッケージのサブプログラムを使用) -
CHANGE
NOTIFICATION
システム権限 -
登録する各オブジェクトでの
READ
またはSELECT
権限
登録済の問合せに関連付けられているオブジェクトに対する権限を失うと、通知が生成されます。
-
-
SYS以外のユーザーとして接続する必要があります。
-
コミットされていないトランザクションの途中でない必要があります。
-
dml_locks
init
.ora
パラメータの値がゼロ以外である必要があります(デフォルト値はゼロ以外です)。(これは、通知を受信するための前提条件でもあります。)
ノート:
QRCNを使用するには、データベースのCOMPATIBLE
設定が11.0.0以上である必要があります。
関連項目:
18.7.4 オブジェクト変更通知(OCN)に対して登録可能な問合せ
ストアド・プロシージャやREF
カーソルの一部として実行される問合せなど、ほとんどの問合せをOCNに対して登録できます。
OCNに対して登録不可能な問合せは次のとおりです。
-
固定表または固定ビューに対する問合せ
-
ユーザー・ビューに対する問合せ
-
データベース・リンク(dblink)を含む問合せ
-
マテリアライズド・ビューでの問合せ
ノート:
OCN登録ではシノニムを使用できますが、QRCN登録では使用できません。
18.7.5 問合せ結果変更通知(QRCN)に対して登録可能な問合せ
QRCNに対して保証モードで登録可能な問合せ、QRCNに対してベストエフォート・モードでのみ登録可能な問合せ、およびQRCNに対してどちらのモードでも登録不可能な問合せがあります。
トピック:
18.7.5.1 保証モードでQRCNに対して登録可能な問合せ
問合せを保証モードでQRCNに対して登録するには、問合せを次のルールに準拠させる必要があります。
-
参照するすべての列が
NUMBER
またはVARCHAR2
データ型である必要があります。 -
列の式に含まれる算術演算子が次のバイナリ演算子に制限され、そのオペランドが数値データ型の列である必要があります。
-
+
(加算) -
-
(減算、単項マイナスではない) -
*
(乗算) -
/
(除算)
-
-
条件句の比較演算子は次の演算子に制限されます。
-
<
(未満) -
<=
(以下) -
=
(等しい) -
>=
(以上) -
>
(より大きい) -
<>
または!=
(等しくない) -
IS
NULL
-
IS
NOT
NULL
-
-
条件句のBoolean演算子は
AND
、OR
およびNOT
に制限されます。 -
問合せに集計関数(
SUM
、COUNT
、AVERAGE
、MIN
、MAX
など)は含められません。
保証モードでは、単一表および一部の内部等価結合に対するほとんどの問合せが可能です(次の例を参照)。
SELECT SALARY FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID AND DEPARTMENTS.LOCATION_ID = 1700;
ノート:
-
問合せオプティマイザで使用される実行計画により、問合せが保証モードでは矛盾する場合があります(
OR
拡張など)。 -
保証モードで登録可能な問合せは、ベストエフォート・モードでも登録可能ですが、ベストエフォート・モードではCQNで簡略化されない問合せに対しても誤検出が発生する可能性があるため、結果は異なる場合があります。
関連項目:
-
SQL集計関数のリストは、『Oracle Database SQL言語リファレンス』を参照してください。
-
問合せオプティマイザの詳細は、『Oracle Database SQLチューニング・ガイド』を参照してください。
18.7.5.2 ベストエフォート・モードでのみQRCNに対して登録可能な問合せ
次のいずれかの条件を満たす問合せは、ベストエフォート・モードでのみQRCNに対して登録可能であり、その簡略バージョンでは通知がオブジェクトの粒度で生成されます。
-
暗号化が有効にされている列を参照する問合せ
-
同じ型の項目が
SELECT
構文のリストに11以上ある問合せ -
次のいずれかを使用した式を含む問合せ
-
文字列関数(
SUBSTR
、LTRIM
、RTRIM
など) -
算術関数(
TRUNC
、ABS
、SQRT
など) -
パターン一致条件
LIKE
およびREGEXP_LIKE
-
EXISTS
またはNOT
EXISTS
条件
-
-
別の表の列で定義されている条件句を使用した論理和を含む問合せ。たとえば:
SELECT EMPLOYEE_ID, DEPARTMENT_ID FROM EMPLOYEES, DEPARTMENTS WHERE EMPLOYEES.EMPLOYEE_ID = 10 OR DEPARTMENTS.DEPARTMENT_ID = 'IT';
-
ユーザーROWIDアクセスを含む問合せ。たとえば:
SELECT DEPARTMENT_ID FROM DEPARTMENTS WHERE ROWID = 'AAANkdAABAAALinAAF';
-
内部結合以外の結合を含む問合せ
-
次のいずれかを使用した実行計画を含む問合せ
-
ビットマップ結合索引、ドメイン索引またはファンクション索引
-
UNION
ALL
またはCONCATENATION
(問合せ自体の内部、または問合せオプティマイザで選択された
OR
拡張実行計画の結果内で使用。) -
ORDER
BY
またはGROUP
BY
(問合せ自体の内部、または問合せオプティマイザで選択された実行計画内の
ORDER
BY
オプションを使用したSORT
操作の結果内で使用。) -
オーバーフロー・セグメントがあり、パーティション化された索引構成表(IOT)
-
クラスタ・オブジェクト
-
パラレル実行
-
関連項目:
SQL関数のリストは、『Oracle Database SQL言語リファレンス』を参照してください。
18.7.5.3 どちらのモードでもQRCNに対して登録不可能な問合せ
次のいずれかを参照する問合せは、保証モードとベストエフォート・モードのどちらでもQRCNに対して登録不可能です。
-
ビュー
-
固定表、リモート表または仮想プライベート・データベース(VPD)ポリシーが有効化されている表
-
DUAL
(SELECT
構文のリスト内) -
シノニム
-
ユーザー定義のPL/SQLサブプログラムのコール
-
「保証モードでQRCNに対して登録可能な問合せ」にリストされていない演算子
-
集計関数
COUNT
(他の集計関数は、ベストエフォート・モードでは登録可能ですが、保証モードでは登録不可能です。)
-
アプリケーション・コンテキスト。たとえば:
SELECT SALARY FROM EMPLOYEES WHERE USER = SYS_CONTEXT('USERENV', 'SESSION_USER');
-
SYSDATE
、SYSTIMESTAMP
またはCURRENT
TIMESTAMP
また、マテリアライズド・ビューを使用して問合せオプティマイザがリライトした問合せはQRCNに対して登録できません。
関連項目:
問合せオプティマイザの詳細は、『Oracle Database SQLチューニング・ガイド』を参照してください。
18.7.6 PL/SQLを介したCQNに対する問合せの登録
PL/SQLを使用してCQN登録を作成するステップは、次のとおりです。
18.7.6.1 PL/SQL通知ハンドラの作成
通知ハンドラとして使用するPL/SQLストアド・プロシージャの作成時には、次のシグネチャを含める必要があります。
PROCEDURE schema_name.proc_name(ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR)
このシグネチャで、schema_name
はデータベース・スキーマ名、proc_name
はストアド・プロシージャ名、ntfnds
は通知記述子を示します。
通知記述子はCQ_NOTIFICATION$_DESCRIPTOR
オブジェクトであり、その属性によって変更の詳細(トランザクションID、変更のタイプ、影響を受けた問合せ、変更された表など)が記述されます。
通知記述子ntfnds
はJOBQ
プロセスから通知ハンドラproc_name
に渡され、このハンドラがアプリケーション要件に従って通知を処理します。(これは図18-2のステップ6に示します。)
ノート:
通知ハンドラは、ジョブ・キュー・プロセス内で実行されます。JOB_QUEUE_PROCESSES
初期化パラメータは、ジョブの実行用に作成できる最大プロセス数を指定します。PL/SQL通知を受信するには、JOB_QUEUE_PROCESSES
をゼロ以外の値に設定する必要があります。
関連項目:
18.7.6.2 CQ_NOTIFICATION$_REG_INFOオブジェクトの作成
タイプCQ_NOTIFICATION$_REG_INFO
のオブジェクトでは、登録済オブジェクトの変更時にデータベースで実行される通知ハンドラを指定します。SQL*Plusでは、次の文を実行してそのタイプ属性を表示できます。
DESC CQ_NOTIFICATION$_REG_INFO
表18-2に、SYS
.CQ_NOTIFICATION$_REG_INFO
の属性を示します。
表18-2 CQ_NOTIFICATION$_REG_INFOの属性
属性 | 説明: |
---|---|
|
通知の生成時に実行されるPL/SQLプロシージャ(通知ハンドラ)の名前を指定します。この名前は、 |
|
1つ以上のサービスのクオリティ・フラグ( 複数のサービスのクオリティ・フラグを指定するには、ビット単位の |
|
登録のタイムアウト期間を指定します。0(ゼロ)以外の値に設定する場合は、データベースにより登録がパージされるまでの秒数を指定します。
|
|
OCN(「オブジェクト変更通知(OCN)について」を参照)にのみ適用されます。 SQL文の型に基づいてメッセージをフィルタリングします。
ビット単位の |
|
非推奨。フロー制御の通知を実装するには、 OCN(「オブジェクト変更通知(OCN)について」を参照)にのみ適用されます。 クライアントがデータベースから遅れることのできるトランザクション数またはデータベース変更数を指定します。0の場合、クライアントは無効化メッセージを生成直後に受信します。5の場合、登録済オブジェクトを変更するトランザクションのうち5番目ごとに通知が送信されます。データベースでは、中間の変更がオブジェクトの粒度で追跡され、変更が通知とともにバンドルされます。そのため、クライアントから中間の変更が失われることはありません。 トランザクションのコミット時にオブジェクトの変更通知をそれ以上の遅延なしで受信する必要のあるほとんどのアプリケーションでは、トランザクション・ラグ0(ゼロ)を選択するように期待されます。ゼロ以外のトランザクション・ラグが役立つのは、アプリケーションで通知に対するフロー制御を実装している場合のみです。ゼロ以外のトランザクション・ラグを使用する場合は、アプリケーションのワークロードのプロパティを、通知が妥当な頻度で生成されるように設定することをお薦めします。そうしないと、ラグに達するまで通知が無限に遅延する可能性があります。
|
|
通知をグループ化するクラスを指定します。使用可能な値は |
|
グループを定義する時間間隔を秒単位で指定します。たとえば、この値が900の場合、同じ15分間隔内に生成された通知がグループ化されます。 |
|
次のいずれかのグループ化のタイプを指定します。
|
|
通知生成の開始時間を指定します。 |
|
通知を繰り返す回数を指定します。登録の存続中に通知を永続的に受信するには、 |
表18-3のサービスのクオリティ・フラグは、DBMS_CQ_NOTIFICATION
パッケージ内の定数です。このフラグは、CQ_NOTIFICATION$_REG_INFO
のQOS_FLAGS
属性を使用して指定できます(表18-2 を参照)。
表18-3 サービスのクオリティ・フラグ
フラグ | 説明: |
---|---|
|
最初の通知後に登録をパージします。 |
|
通知を永続データベース・キューに格納します。 Oracle RAC環境では、データベース・インスタンスで障害が発生すると、正常なデータベース・インスタンスがキューにある通知メッセージを配信できます。 デフォルト: 通知はより効率のよい共有メモリーに格納されます。 |
|
変更された各行の |
|
QRCNに対する問合せを登録します(「問合せ結果変更通知(QRCN)について」を参照)。 QRCNに対する問合せを登録できない場合は、 デフォルト: OCNに対する問合せが登録されます(「オブジェクト変更通知(OCN)について」を参照) |
|
どの問合せが簡略化されているかを確認するには、静的データ・ディクショナリ・ビュー デフォルト: QRCNに対して問合せが保証モードで登録されます(「保証モード」を参照) |
登録済オブジェクトに変更があるたびに、プロシージャHR
.dcn_callback
を起動する必要があるとします。例18-4では、HR
.dcn_callback
が通知を受け取るように指定するCQ_NOTIFICATION$_REG_INFO
オブジェクトを作成します。このオブジェクトを作成するには、DBMS_CQ_NOTIFICATION
パッケージに対するEXECUTE
権限が必要です。
例18-4 CQ_NOTIFICATION$_REG_INFOオブジェクトの作成
DECLARE v_cn_addr CQ_NOTIFICATION$_REG_INFO; BEGIN -- Create object: v_cn_addr := CQ_NOTIFICATION$_REG_INFO ( 'HR.dcn_callback', -- PL/SQL notification handler DBMS_CQ_NOTIFICATION.QOS_QUERY -- notification type QRCN + DBMS_CQ_NOTIFICATION.QOS_ROWIDS, -- include rowids of changed objects 0, -- registration persists until unregistered 0, -- notify on all operations 0 -- notify immediately ); -- Register queries: ... END; /
18.7.6.3 通知内の各問合せの識別
問合せの登録済リストに含まれる問合せにより、連続問合せ通知が生成される場合があります。特定の問合せによっていつ通知が生成されるかを確認するには、その問合せのSELECT
構文のリストでDBMS_CQ_NOTIFICATION
.CQ_NOTIFICATION_QUERYID
ファンクションを使用します。たとえば:
SELECT EMPLOYEE_ID, SALARY, DBMS_CQ_NOTIFICATION.CQ_NOTIFICATION_QUERYID
FROM EMPLOYEES
WHERE DEPARTMENT_ID = 10;
結果:
EMPLOYEE_ID SALARY CQ_NOTIFICATION_QUERYID ----------- ---------- ----------------------- 200 2800 0 1 row selected.
問合せによって通知が生成される場合、その通知には問合せIDが含まれます。
18.7.6.4 既存の登録への問合せの追加
問合せを既存の登録に追加するステップは、次のとおりです。
例18-5 既存の登録への問合せの追加
DECLARE v_cursor SYS_REFCURSOR; BEGIN -- Open existing registration DBMS_CQ_NOTIFICATION.ENABLE_REG(21); OPEN v_cursor FOR -- Run query to be registered SELECT DEPARTMENT_ID FROM HR.DEPARTMENTS; -- register this query CLOSE v_cursor; -- Close registration DBMS_CQ_NOTIFICATION.REG_END; END; /
18.7.7 CQN登録のベスト・プラクティス
CQNに最適なパフォーマンスを得るには、次の登録ガイドラインに従ってください。
-
登録する問合せの数を少なくします。可能であれば、ほとんど変更されないオブジェクトを参照する問合せを登録します。
オブジェクトの揮発性が非常に高いと、多数の通知が送信されることになり、そのオーバーヘッドによってOLTPスループットが低下します。
-
同じ通知メッセージが複数の受信者宛に複製されないように、特定のオブジェクトについて重複登録の数を最小限に抑えます。
18.7.8 CQN登録のトラブルシューティング
登録を作成できない場合、または登録を作成しても予期した通知を受信していない場合は、次のような原因が考えられます。
-
JOB_QUEUE_PROCESSES
パラメータがゼロ以外の値に設定されていません。このため、通知ハンドラを介してPL/SQL通知を受信できません。
-
登録の作成時にSYSユーザーとして接続しました。
CQN登録を作成するには、SYS以外のユーザーとして接続する必要があります。
-
登録済オブジェクトを変更した後で、そのトランザクションをコミットしていません。
通知は、トランザクションのコミット時にのみ生成されます。
-
登録がデータベースに正常に作成されていません。
確認するには、静的データ・ディクショナリ・ビュー
*_CHANGE_NOTIFICATION_REGS
を問い合せます。たとえば、次の文により、現行ユーザーに関するすべての登録と登録済オブジェクトが表示されます。SELECT REGID, TABLE_NAME FROM USER_CHANGE_NOTIFICATION_REGS;
-
通知ハンドラの実行中に実行時エラーが発生しました。
その場合は、プロシージャの実行を試行した
JOBQ
プロセスのトレース・ファイルに記録されます。通常、トレース・ファイルの名前は次の形式になります。ORACLE_SID_jnumber_PID.trc
たとえば、ORACLE_SIDが
dbs1
で、JOBQ
プロセスのプロセスID(PID)が12483の場合、トレース・ファイルの名前は通常、dbs1_j000_12483
.trc
となります。通知ハンドラに'
chnf_callback
'、登録IDに100を指定して登録が作成されているとします。ここで、'chnf_callback
'はデータベースに定義されていないとします。この場合、JOBQ
トレース・ファイルには次の書式のメッセージが含まれます。**************************************************************************** Runtime error during execution of PL/SQL cbk chnf_callback for reg CHNF100. Error in PLSQL notification of msgid: Queue : Consumer Name : PLSQL function :chnf_callback Exception Occured, Error msg: ORA-00604: error occurred at recursive SQL level 2 ORA-06550: line 1, column 7: PLS-00201: identifier 'CHNF_CALLBACK' must be declared ORA-06550: line 1, column 7: PL/SQL: Statement ignored ****************************************************************************
通知ハンドラの実行中に実行時エラーが発生した場合は、通知を受信していることを確認するために、簡略バージョンの通知ハンドラを作成してから、アプリケーション・ロジックを徐々に追加してください。
非常に単純な通知ハンドラの例は次のとおりです。
REM Create table in HR schema to hold count of notifications received. CREATE TABLE nfcount(cnt NUMBER); INSERT INTO nfcount (cnt) VALUES(0); COMMIT; CREATE OR REPLACE PROCEDURE chnf_callback (ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR) IS BEGIN UPDATE nfcount SET cnt = cnt+1; COMMIT; END; /
-
トランザクションがコミットされてからエンド・ユーザーが通知を受信するまでに、タイム・ラグが発生しています。
18.7.9 登録の削除
登録を削除するには、登録IDをパラメータとして使用してプロシージャDBMS_CQ_NOTIFICATION
.DEREGISTER
をコールします。たとえば、次の文により、登録IDが21の登録が解除されます。
DBMS_CQ_NOTIFICATION.DEREGISTER(21);
登録を作成したユーザーまたはSYSユーザーのみが登録を解除できます。
18.7.10 CQNの構成: 使用例
この使用例では、開発者として従業員データ(名前、所在地、電話番号など)を提供するWebアプリケーションを管理しているとします。このアプリケーションはOracle Application Serverで実行され、使用量が多く、バックエンド・データベースにあるHR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表の頻繁な問合せを処理します。この2つの表はあまり変更されないため、アプリケーションでは問合せ結果をキャッシュすることでパフォーマンスを改善できます。キャッシュにより、バックエンド・データベースへのラウンド・トリップ、およびサーバー側での実行待機時間が回避されます。
DBMS_CQ_NOTIFICATION
パッケージを使用すると、HR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表に基づく問合せを登録できます。CQNを構成するステップは、次のとおりです。
- 「PL/SQL通知ハンドラの作成」の説明に従って、通知を処理するサーバー側PL/SQLストアド・プロシージャを作成します。
- 「問合せの登録」の説明に従って、
HR
.EMPLOYEES
表およびHR
.DEPARTMENTS
表へのQRCNに対する問合せを登録します。
18.7.10.1 PL/SQL通知ハンドラの作成
次の説明に従って、通知を処理するサーバー側ストアドPL/SQLプロシージャを作成します。
例18-6 サーバー側PL/SQL通知ハンドラの作成
CREATE OR REPLACE PROCEDURE chnf_callback ( ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR ) IS regid NUMBER; tbname VARCHAR2(60); event_type NUMBER; numtables NUMBER; operation_type NUMBER; numrows NUMBER; row_id VARCHAR2(2000); numqueries NUMBER; qid NUMBER; qop NUMBER; BEGIN regid := ntfnds.registration_id; event_type := ntfnds.event_type; INSERT INTO nfevents (regid, event_type) VALUES (chnf_callback.regid, chnf_callback.event_type); numqueries :=0; IF (event_type = DBMS_CQ_NOTIFICATION.EVENT_QUERYCHANGE) THEN numqueries := ntfnds.query_desc_array.count; FOR i IN 1..numqueries LOOP -- loop over queries qid := ntfnds.query_desc_array(i).queryid; qop := ntfnds.query_desc_array(i).queryop; INSERT INTO nfqueries (qid, qop) VALUES(chnf_callback.qid, chnf_callback.qop); numtables := 0; numtables := ntfnds.query_desc_array(i).table_desc_array.count; FOR j IN 1..numtables LOOP -- loop over tables tbname := ntfnds.query_desc_array(i).table_desc_array(j).table_name; operation_type := ntfnds.query_desc_array(i).table_desc_array(j).Opflags; INSERT INTO nftablechanges (qid, table_name, table_operation) VALUES ( chnf_callback.qid, tbname, operation_type ); IF (bitand(operation_type, DBMS_CQ_NOTIFICATION.ALL_ROWS) = 0) THEN numrows := ntfnds.query_desc_array(i).table_desc_array(j).numrows; ELSE numrows :=0; -- ROWID info not available END IF; -- Body of loop does not run when numrows is zero. FOR k IN 1..numrows LOOP -- loop over rows Row_id := ntfnds.query_desc_array(i).table_desc_array(j).row_desc_array(k).row_id; INSERT INTO nfrowchanges (qid, table_name, row_id) VALUES (chnf_callback.qid, tbname, chnf_callback.Row_id); END LOOP; -- loop over rows END LOOP; -- loop over tables END LOOP; -- loop over queries END IF; COMMIT; END; /
18.7.10.2 問合せの登録
通知ハンドラの作成後に、例18-7に示すように、HR
.chnf_callback
を通知ハンドラとして指定して、通知を受信する対象となる問合せを登録します。
例18-7 問合せの登録
DECLARE reginfo CQ_NOTIFICATION$_REG_INFO; mgr_id NUMBER; dept_id NUMBER; v_cursor SYS_REFCURSOR; regid NUMBER; BEGIN /* Register two queries for QRNC: */ /* 1. Construct registration information. chnf_callback is name of notification handler. QOS_QUERY specifies result-set-change notifications. */ reginfo := cq_notification$_reg_info ( 'chnf_callback', DBMS_CQ_NOTIFICATION.QOS_QUERY, 0, 0, 0 ); /* 2. Create registration. */ regid := DBMS_CQ_NOTIFICATION.new_reg_start(reginfo); OPEN v_cursor FOR SELECT dbms_cq_notification.CQ_NOTIFICATION_QUERYID, manager_id FROM HR.EMPLOYEES WHERE employee_id = 7902; CLOSE v_cursor; OPEN v_cursor FOR SELECT dbms_cq_notification.CQ_NOTIFICATION_QUERYID, department_id FROM HR.departments WHERE department_name = 'IT'; CLOSE v_cursor; DBMS_CQ_NOTIFICATION.reg_end; END; /
新しく作成した登録を表示:
SELECT queryid, regid, TO_CHAR(querytext) FROM user_cq_notification_queries;
結果は次のようになります。
QUERYID REGID TO_CHAR(QUERYTEXT) ------- ----- ------------------------------------------------ 22 41 SELECT HR.DEPARTMENTS.DEPARTMENT_ID FROM HR.DEPARTMENTS WHERE HR.DEPARTMENTS.DEPARTMENT_NAME = 'IT' 21 41 SELECT HR.EMPLOYEES.MANAGER_ID FROM HR.EMPLOYEES WHERE HR.EMPLOYEES.EMPLOYEE_ID = 7902
次のトランザクションを実行すると、QUERYID
が22の問合せの結果が変更されます。
UPDATE DEPARTMENTS SET DEPARTMENT_NAME = 'FINANCE' WHERE department_name = 'IT';
通知プロシージャchnf_callback
(例18-6で作成したプロシージャ)が実行されます。
通知イベントが記録される表を問合せ:
SELECT * FROM nfevents;
結果は次のようになります。
REGID EVENT_TYPE ----- ---------- 61 7
EVENT_TYPE
7は、EVENT_QUERYCHANGE
(問合せ結果変更)に対応します。
登録済の表への変更が記録される表を問い合せます。
SELECT * FROM nftablechanges;
結果は次のようになります。
REGID TABLE_NAME TABLE_OPERATION ----- -------------- --------------- 42 HR.DEPARTMENTS 4
TABLE_OPERATION
4は、UPDATEOP
(更新操作)に対応します。
変更された行のROWID
が記録される表を問い合せます。
SELECT * FROM nfrowchanges;
結果は次のようになります。
REGID TABLE_NAME ROWID ----- -------------- ------------------ 61 HR.DEPARTMENTS AAANkdAABAAALinAAF
18.8 OCIを使用したCQN登録の作成
この項では、OCIを使用したCQN登録の作成について説明します。OCIを使用する場合、通知ハンドラはクライアント側Cコールバック・プロシージャです。
トピック
関連項目:
OCIのパブリッシュ・サブスクライブ通知の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
18.8.1 問合せ結果セット通知でのOCI使用
連続問合せ(CQ)通知固有のQOS(サービス品質フラグ)を記録するには、サブスクリプション・ハンドルOCI_HTYPE_SUBSCR
に属性OCI_ATTR_SUBSCR_CQ_QOSFLAGS
を設定します。オブジェクトの細分化レベルではなく問合せの細分化レベルで登録するようにリクエストするには、属性OCI_ATTR_SUBSCR_CQ_QOSFLAGS
にOCI_SUBSCR_CQ_QOS_QUERY
フラグ・ビットを設定します。
オプションで擬似列CQ_NOTIFICATION_QUERY_ID
を指定して、登録済問合せの問合せIDを取得できます。この場合、細分化レベルは自動的には問合せレベルに変換されません。戻り時の擬似列の値は、問合せに割り当てられた一意の問合せIDに設定されます。OCIベースの登録の場合は問合せIDの擬似列を省略できますが、その場合は問合せIDが文ハンドルのREAD
属性として送信されます。(この属性はOCI_ATTR_CQ_QUERYID
です)。
通知中に、クライアントが指定したコールバックが起動され、トップレベルの通知記述子が引数として渡されます。
変更された問合せの問合せIDに関する情報は、OCI_DTYPE_CQDES
という特別な記述型を使用して送られます。問合せ記述子の集合(OCIColl
)は、トップレベルの通知記述子内に埋め込まれます。各記述子はOCI_DTYPE_CQDES
型です。この問合せ記述子には、次の属性が含まれています。
-
OCI_ATTR_CQDES_OPERATION
-OCI_EVENT_QUERYCHANGE
またはOCI_EVENT_DEREG
です。 -
OCI_ATTR_CQDES_QUERYID
- 変更された問合せの問合せIDです。 -
OCI_ATTR_CQDES_TABLE_CHANGES
- 問合せ結果セットが変更される原因となった表に対するDML操作を記述する表記述子の配列です。各表記述子はOCI_DTYPE_TABLE_CHDES
型です。
関連項目:
18.8.2 連続問合せ通知の登録のためのOCI使用
コール側セッションには、登録対象のすべてのオブジェクトに対するCHANGE
NOTIFICATION
システム権限およびSELECT
権限が必要です。登録はデータベースに記録される永続エンティティであり、Oracle RACのすべてのインスタンスに表示されます。登録が問合せ精度の場合、Oracle RACインスタンスで問合せ結果セットが変更され、コミットされる原因となるトランザクションによって通知が生成されます。登録がオブジェクト精度の場合、Oracle RACインスタンスに登録済のオブジェクトを変更するトランザクションによって通知が生成されます。
マテリアライズド・ビューまたは非マテリアライズド・ビューが関係する問合せは、サポートされません。
登録インタフェースは、コールバック、およびAQのネームスペース拡張(DBCHANGE
)を使用して、問合せ対象オブジェクトの変更を通知します。
登録を作成するステップは、次のとおりです。
サブスクリプション・ハンドルへの文ハンドルのバインドが有効なのは、問合せの初回の実行時のみです。アプリケーションで後続の実行時に同じOCI statement文ハンドルを使用する必要がある場合は、文ハンドルの登録ハンドル属性に値を再移入する必要があります。文ハンドルをサブスクリプション・ハンドルにバインドできるのは、文が問合せである場合のみです(実行時に判別されます)。実行の一部としてDML文が実行されると、例外が発行されます。
18.8.3 クライアントが開始したCQN登録でのOCIの使用
アプリケーションでは、クライアントが開始した接続モードを使用して、変更問合せ通知(CQN)を登録および受信できます。
このモードは、クラウドのアプリケーションで動作するように設計されていますが、オンプレミスで実行されているアプリケーションにも使用できます。この通知配信モードでは、クライアント・アプリケーションは通知を受信するためにOracleデータベース・サーバーへの接続を開始します。データベース・サーバーとアプリケーションを接続する必要はありません。クライアントが開始した接続は、特別なネットワーク構成を必要とせず、使いやすくセキュアです。
CQN登録を開始するには、アプリケーション・クライアントでOCIインタフェースを使用できます。モードの値がOCI_SECURE_NOTIFICATION
に設定されたOCISubscriptionRegister()
関数をコールし、アプリケーション・クライアントを登録します。登録を削除するには、モードの値がOCI_SECURE_NOTIFICATION
に設定されたOCISubscriptionUnRegister()
を使用します。
関連項目:
OCISubscriptionRegister()およびOCISubscriptionUnRegister()の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
18.8.4 連続問合せ通知のOCIサブスクリプション・ハンドル属性の使用
連続問合せ通知のサブスクリプション・ハンドル属性は、汎用属性(すべてのサブスクリプションに対して共通)と、連続問合せ通知に固有のネームスペース特有属性に分けられます。
文ハンドルのWRITE
属性を変更できるのは、登録の作成前のみです。
汎用属性 - すべてのサブスクリプションに共通
OCI_ATTR_SUBSCR_NAMESPACE
(WRITE
) - サブスクリプション・ハンドルの場合、OCI_SUBSCR_NAMESPACE_DBCHANGE
に、この属性を設定します。
OCI_ATTR_SUBSCR_CALLBACK
(WRITE
) - サブスクリプション・ハンドルに関連するコールバックを格納するために、この属性を使用します。通知を受信すると、コールバックが実行されます。
新しい連続問合せ通知メッセージが作成されると、無効化に関する詳細情報が含まれるOCI_DTYPE_CHDES
型の記述子がdesc
に設定されてコールバックがリスナー・スレッド内で呼び出されます。
OCI_ATTR_SUBSCR_QOSFLAGS
: この属性は次の値を含む汎用フラグです。
#define OCI_SUBSCR_QOS_RELIABLE 0x01 /* reliable */ #define OCI_SUBSCR_QOS_PURGE_ON_NTFN 0x10 /* purge on first ntfn */
-
OCI_SUBSCR_QOS_RELIABLE
- このビットを設定し、通知を持続させます。このため、Oracle RACクラスタの残存インスタンスを使用して、ノードの失敗後も無効化メッセージを送信および取得できます。これは、この登録IDに関連する無効化が、データベースに永続的にキューされるためです。このビットがFALSE
の場合、無効化は高速インメモリー・キューにエンキューされます。このオプションは、登録の永続性ではなく、通知の永続性を記述します。デフォルトでは、登録は自動的に持続されます。 -
OCI_SUBSCR_QOS_PURGE_ON_NTFN
- このビットを設定し、最初の通知で登録をパージできるようにします。
パラレルのサンプルは、『Oracle Call Interfaceプログラマーズ・ガイド』のOCIのパブリッシュ・サブスクライブ登録機能に関する項を参照してください。
OCI_ATTR_SUBSCR_CQ_QOSFLAGS
: この属性では、連続問合せ通知固有の次のQOSフラグを記述します(モードはWRITE
、データ型はub4
)。
-
0x1 OCI_SUBSCR_CQ_QOS_QUERY
- このフラグを設定し、問合せレベルの細分性が必須であることを示します。通知は、問合せ結果セットが変更される場合にのみ生成します。デフォルトでは、このQOSレベルにFalseの正数はありません。 -
0x2 OCI_SUBSCR_CQ_QOS_BEST_EFFORT
- このフラグを設定し、ベスト・エフォート・フィルタリングが許容されることを示します。アプリケーションをキャッシュすることで使用できます。データベースでは、評価のコストに基づく経験則を使用し、完全プルーニングを回避できる場合があります。
OCI_ATTR_SUBSCR_TIMEOUT
- この属性を使用して、秒単位で定義したub4
タイムアウト値を指定します。タイムアウト値が0(ゼロ)または未指定の場合、登録は明示的に解除されるまで有効です。
ネームスペース固有または機能固有の属性
次の属性は、ネームスペース固有または連続問合せ通知機能固有のものです。
OCI_ATTR_CHNF_TABLENAMES
(データ型は(OCIColl *)
) - これらは、登録された表名のリストを取得するための属性です。これらの属性は、問合せの実行後にサブスクリプション・ハンドルから使用できます。
OCI_ATTR_CHNF_ROWIDS
- ブール型属性(デフォルトはFALSE
)です。TRUE
の場合、連続問合せ通知メッセージには、操作タイプやROWID
などの行レベルの詳細が含まれます。
OCI_ATTR_CHNF_OPERATIONS
- ub4
フラグを使用して、操作タイプに基づいて通知をフィルタで選択します。登録が問合せレベルの細分性を持つ場合、このオプションは無視されます。用意されているフラグは、次のとおりです。
-
OCI_OPCODE_ALL
- すべての操作 -
OCI_OPCODE_INSERT
- 表に対する挿入操作 -
OCI_OPCODE_UPDATE
- 表に対する更新操作 -
OCI_OPCODE_DELETE
- 表に対する削除操作
OCI_ATTR_CHNF_CHANGELAG
- クライアントはこのub4
値を使用して、遅延させるトランザクション数を指定できます。クライアントは、このオプションを連続問合せ通知メッセージのスロットル・メカニズムとしても使用できます。このオプションを選択すると、OCI_ATTR_CHNF_ROWIDS
がTRUE
であっても、通知にROWID
レベルの細分性を持つ情報が含まれなくなります。登録が問合せレベルの細分性を持つ場合、このオプションは無視されます。
OCISubscriptionRegister()のコールが起動されると、先行する属性(汎用、名前固有、または機能固有)はすべて、作成済の登録では変更不可になります。これらの属性への変更は、すでに作成されている登録には反映されませんが、新しく作成された、同じ登録ハンドルを使用する登録には反映されます。
関連項目:
連続問合せ通知の記述子の属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
NTFNグループ化オプションを使用して、通知の間隔を置くことができます。関連する汎用通知属性は次のとおりです。
OCI_ATTR_SUBSCR_NTFN_GROUPING_VALUE OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE OCI_ATTR_SUBSCR_NTFN_GROUPING_START_TIME OCI_ATTR_SUBSCR_NTFN_GROUPING_REPEAT_COUNT
関連項目:
データベースへの直接パブリッシュ・サブスクライブ登録のこれらの属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
18.8.5 OCI_ATTR_CQ_QUERYID属性
OCIStmtExecute()
のコールによる登録後、文ハンドルOCI_HTYPE_STMT
のOCI_ATTR_CQ_QUERYID
属性が登録済問合せの問合せIDを取得します。
関連項目:
OCI_ATTR_CQ_QUERYID
の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
18.8.6 OCI連続問合せ通知の記述子の使用
連続問合せ通知の記述子は、アプリケーションによって指定された通知コールバックのdesc
パラメータに渡されます。次の属性は、連続問合せ通知に固有のものです。連続問合せ通知の記述子のOCI型定数は、OCI_DTYPE_CHDES
です。
通知コールバックは、トップレベルの通知記述子OCI_DTYPE_CHDES
を引数として受け取ります。この記述子には、イベント・タイプがOCI_EVENT_QUERYCHANGE
であるかOCI_EVENT_OBJCHANGE
であるかに基づいて、OCI_DTYPE_CQDES
またはOCI_DTYPE_TABLE_CHDES
記述子のコレクションが含まれます。OCI_EVENT_QUERYCHANGE
型の通知の場合、連続問合せ記述子には表連続問合せ記述子の配列が埋め込まれます。ROWID
レベルの細分性を持つ情報がリクエストされた場合、各OCI_DTYPE_TABLE_CHDES
には変更された各ROWID
に対応する行レベルの連続問合せ記述子(OCI_DTYPE_ROW_CHDES
)の配列が含まれます。
18.8.6.1 OCI_DTYPE_CHDES
これはトップレベルの連続問合せ通知記述子型です。
OCI_ATTR_CHDES_DBNAME
(oratext *
) - データベースの名前(連続問合せ通知のソース)
OCI_ATTR_CHDES_XID
(RAW(8)
) - メッセージのメッセージID
OCI_ATTR_CHDES_NFYTYPE
- 通知タイプを示すフラグ。
-
0x0 OCI_EVENT_NONE
- 連続問合せ通知に関する追加情報なし -
0x1 OCI_EVENT_STARTUP
- インスタンスの起動 -
0x2 OCI_EVENT_SHUTDOWN
- インスタンスの停止 -
0x3 OCI_EVENT_SHUTDOWN_ANY
- 任意のインスタンスの停止 - Oracle Real Application Clusters (RAC) -
0x5 OCI_EVENT_DEREG
- 登録解除またはタイムアウト -
0x6 OCI_EVENT_OBJCHANGE
- オブジェクト変更通知 -
0x7 OCI_EVENT_QUERYCHANGE
- 問合せ変更通知
OCI_ATTR_CHDES_TABLE_CHANGES
- 表またはデータ型(OCIColl *)
の操作を記述するコレクション型。この属性は、OCI_ATTR_CHDES_NFTYPE
属性がOCI_EVENT_OBJCHANGE
型の場合のみ表示され、そうでない場合はNULL
になります。コレクションの各要素は、OCI_DTYPE_TABLE_CHDES
型の連続問合せ記述子の表です。
OCI_ATTR_CHDES_QUERIES
- 無効化された問合せを記述するコレクション型。コレクションの各メンバーはOCI_DTYPE_CQDES
型です。この属性は、OCI_ATTR_CHDES_NFTYPE
属性がOCI_EVENT_QUERYCHANGE
型の場合のみ表示され、そうでない場合はNULL
になります。
18.8.6.1.1 OCI_DTYPE_CQDES
この通知記述子は、通常はDMLトランザクションまたはDDLトランザクションのコミットに応じて、無効化された問合せを記述します。次の属性が含まれます。
-
OCI_ATTR_CQDES_OPERATION
(ub4
,READ
) - 問合せで発生した操作。これは次の値のいずれかです。-
OCI_EVENT_QUERYCHANGE
- 問合せ結果セットの変更 -
OCI_EVENT_DEREG
- 問合せの登録解除
-
-
OCI_ATTR_CQDES_TABLE_CHANGES
(OCIColl *
,READ
) - 問合せ結果セットの変更原因となった表に対するDMLまたはDDL操作を記述する、表連続問合せ記述子のコレクション。コレクションの各要素はOCI_DTYPE_TABLE_CHDES
型です。 -
OCI_ATTR_CQDES_QUERYID
(ub8
,READ
) - 無効化された問合せの問合せID。
18.8.6.1.2 OCI_DTYPE_TABLE_CHDES
この通知記述子は、登録済問合せに関係する表の変更内容に関する情報を送信します。次の属性が含まれます。
-
OCI_ATTR_CHDES_TABLE_NAME
(oratext *)
- スキーマ注釈付き表名。 -
OCI_ATTR_CHDES_TABLE_OPFLAGS
(ub4
)- 表への操作を示すフラグのフィールド。次の各フラグ・フィールドは、属性内の個別のビット位置にあります。-
0x1 OCI_OPCODE_ALLROWS
- 表は完全に無効化されています。 -
0x2 OCI_OPCODE_INSERT
- 表に対する挿入操作。 -
0x4 OCI_OPCODE_UPDATE
- 表に対する更新操作。 -
0x8 OCI_OPCODE_DELETE
- 表に対する削除操作。 -
0x10 OCI_OPCODE_ALTER
- 表は変更されました(スキーマ変更)。これには、行の移行を引き起こしたDDL文と内部操作が含まれます。 -
0x20 OCI_OPCODE_DROP
- 表は削除されました。
-
-
OCI_ATTR_CHDES_TABLE_ROW_CHANGES
- これは、表内の行の変更を示す埋込みコレクションです。コレクションの各要素は、次の属性を持つOCI_DTYPE_ROW_CHDES
型の行連続問合せ記述子です。-
OCI_ATTR_CHDES_ROW_ROWID
(OraText *
)-ROWID
の文字列表現。 -
OCI_ATTR_CHDES_ROW_OPFLAGS
- 操作タイプ(INSERT
、UPDATE
、DELETE
またはOTHER
)を反映します。
-
関連項目:
連続問合せ通知の記述子の属性の詳細は、『Oracle Call Interfaceプログラマーズ・ガイド』を参照してください
18.8.7 OCIサンプル・プログラムでの連続問合せ通知のデモンストレーション
例18-8は単純なOCIプログラム、demoquery.c
です。リストにあるコメントを参照してください。コール側セッションには、登録対象のすべてのオブジェクトに対するCHANGE
NOTIFICATION
システム権限およびSELECT
権限が必要です。
例18-8 連続問合せ通知を示すプログラム・リスト
/* Copyright (c) 2010, Oracle. All rights reserved. */
#ifndef S_ORACLE
# include <oratypes.h>
#endif
/**************************************************************************
*This is a DEMO program. To test, compile the file to generate the executable
*demoquery. Then demoquery can be invoked from a command prompt.
*It will have the following output:
Initializing OCI Process
Registering query : select last_name, employees.department_id, department_name
from employees, departments
where employee_id = 200
and employees.department_id = departments.department_id
Query Id 23
Waiting for Notifications
*Then from another session, log in as HR/<password> and perform the following
* DML transactions. It will cause two notifications to be generated.
update departments set department_name ='Global Admin' where department_id=10;
commit;
update departments set department_name ='Administration' where department_id=10;
commit;
*The demoquery program will now show the following output corresponding
*to the notifications received.
Query 23 is changed
Table changed is HR.DEPARTMENTS table_op 4
Row changed is AAAMBoAABAAAKX2AAA row_op 4
Query 23 is changed
Table changed is HR.DEPARTMENTS table_op 4
Row changed is AAAMBoAABAAAKX2AAA row_op 4
*The demo program waits for exactly 10 notifications to be received before
*logging off and unregistering the subscription.
***************************************************************************/
/*---------------------------------------------------------------------------
PRIVATE TYPES AND CONSTANTS
---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------
STATIC FUNCTION DECLARATIONS
---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <oci.h>
#define MAXSTRLENGTH 1024
#define bit(a,b) ((a)&(b))
static int notifications_processed = 0;
static OCISubscription *subhandle1 = (OCISubscription *)0;
static OCISubscription *subhandle2 = (OCISubscription *)0;
static void checker(/*_ OCIError *errhp, sword status _*/);
static void registerQuery(/*_ OCISvcCtx *svchp, OCIError *errhp, OCIStmt *stmthp,
OCIEnv *envhp _*/);
static void myCallback (/*_ dvoid *ctx, OCISubscription *subscrhp,
dvoid *payload, ub4 *payl, dvoid *descriptor,
ub4 mode _*/);
static int NotificationDriver(/*_ int argc, char *argv[] _*/);
static sword status;
static boolean logged_on = FALSE;
static void processRowChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp,
OCIColl *row_changes);
static void processTableChanges(OCIEnv *envhp, OCIError *errhp,
OCIStmt *stmthp, OCIColl *table_changes);
static void processQueryChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp,
OCIColl *query_changes);
static int nonractests2(/*_ int argc, char *argv[] _*/);
int main(int argc, char **argv)
{
NotificationDriver(argc, argv);
return 0;
}
int NotificationDriver(argc, argv)
int argc;
char *argv[];
{
OCIEnv *envhp;
OCISvcCtx *svchp, *svchp2;
OCIError *errhp, *errhp2;
OCISession *authp, *authp2;
OCIStmt *stmthp, *stmthp2;
OCIDuration dur, dur2;
int i;
dvoid *tmp;
OCISession *usrhp;
OCIServer *srvhp;
printf("Initializing OCI Process\n");
/* Initialize the environment. The environment must be initialized
with OCI_EVENTS and OCI_OBJECT to create a continuous query notification
registration and receive notifications.
*/
OCIEnvCreate( (OCIEnv **) &envhp, OCI_EVENTS|OCI_OBJECT, (dvoid *)0,
(dvoid * (*)(dvoid *, size_t)) 0,
(dvoid * (*)(dvoid *, dvoid *, size_t))0,
(void (*)(dvoid *, dvoid *)) 0,
(size_t) 0, (dvoid **) 0 );
OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR,
(size_t) 0, (dvoid **) 0);
/* server contexts */
OCIHandleAlloc((dvoid *) envhp, (dvoid **) &srvhp, OCI_HTYPE_SERVER,
(size_t) 0, (dvoid **) 0);
OCIHandleAlloc((dvoid *) envhp, (dvoid **) &svchp, OCI_HTYPE_SVCCTX,
(size_t) 0, (dvoid **) 0);
checker(errhp,OCIServerAttach(srvhp, errhp, (text *) 0, (sb4) 0,
(ub4) OCI_DEFAULT));
/* set attribute server context in the service context */
OCIAttrSet( (dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX, (dvoid *)srvhp,
(ub4) 0, (ub4) OCI_ATTR_SERVER, (OCIError *) errhp);
/* allocate a user context handle */
OCIHandleAlloc((dvoid *)envhp, (dvoid **)&usrhp, (ub4) OCI_HTYPE_SESSION,
(size_t) 0, (dvoid **) 0);
OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION,
(dvoid *)((text *)"HR"), (ub4)strlen((char *)"HR"),
OCI_ATTR_USERNAME, errhp);
OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION,
(dvoid *)((text *)"HR"), (ub4)strlen((char *)"HR"),
OCI_ATTR_PASSWORD, errhp);
checker(errhp,OCISessionBegin (svchp, errhp, usrhp, OCI_CRED_RDBMS,
OCI_DEFAULT));
/* Allocate a statement handle */
OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &stmthp,
(ub4) OCI_HTYPE_STMT, 52, (dvoid **) &tmp);
OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX, (dvoid *)usrhp, (ub4)0,
OCI_ATTR_SESSION, errhp);
registerQuery(svchp, errhp, stmthp, envhp);
printf("Waiting for Notifications\n");
while (notifications_processed !=10)
{
sleep(1);
}
printf ("Going to unregister HR\n");
fflush(stdout);
/* Unregister HR */
checker(errhp,
OCISubscriptionUnRegister(svchp, subhandle1, errhp, OCI_DEFAULT));
checker(errhp, OCISessionEnd(svchp, errhp, usrhp, (ub4) 0));
printf("HR Logged off.\n");
if (subhandle1)
OCIHandleFree((dvoid *)subhandle1, OCI_HTYPE_SUBSCRIPTION);
if (stmthp)
OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
if (srvhp)
OCIHandleFree((dvoid *) srvhp, (ub4) OCI_HTYPE_SERVER);
if (svchp)
OCIHandleFree((dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX);
if (authp)
OCIHandleFree((dvoid *) usrhp, (ub4) OCI_HTYPE_SESSION);
if (errhp)
OCIHandleFree((dvoid *) errhp, (ub4) OCI_HTYPE_ERROR);
if (envhp)
OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV);
return 0;
}
void checker(errhp, status)
OCIError *errhp;
sword status;
{
text errbuf[512];
sb4 errcode = 0;
int retval = 1;
switch (status)
{
case OCI_SUCCESS:
retval = 0;
break;
case OCI_SUCCESS_WITH_INFO:
(void) printf("Error - OCI_SUCCESS_WITH_INFO\n");
break;
case OCI_NEED_DATA:
(void) printf("Error - OCI_NEED_DATA\n");
break;
case OCI_NO_DATA:
(void) printf("Error - OCI_NODATA\n");
break;
case OCI_ERROR:
(void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
(void) printf("Error - %.*s\n", 512, errbuf);
break;
case OCI_INVALID_HANDLE:
(void) printf("Error - OCI_INVALID_HANDLE\n");
break;
case OCI_STILL_EXECUTING:
(void) printf("Error - OCI_STILL_EXECUTE\n");
break;
case OCI_CONTINUE:
(void) printf("Error - OCI_CONTINUE\n");
break;
default:
break;
}
if (retval)
{
exit(1);
}
}
void processRowChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp,
OCIColl *row_changes)
{
dvoid **row_descp;
dvoid *row_desc;
boolean exist;
ub2 i, j;
dvoid *elemind = (dvoid *)0;
oratext *row_id;
ub4 row_op;
sb4 num_rows;
if (!row_changes) return;
checker(errhp, OCICollSize(envhp, errhp,
(CONST OCIColl *) row_changes, &num_rows));
for (i=0; i<num_rows; i++)
{
checker(errhp, OCICollGetElem(envhp,
errhp, (OCIColl *) row_changes,
i, &exist, &row_descp, &elemind));
row_desc = *row_descp;
checker(errhp, OCIAttrGet (row_desc,
OCI_DTYPE_ROW_CHDES, (dvoid *)&row_id,
NULL, OCI_ATTR_CHDES_ROW_ROWID, errhp));
checker(errhp, OCIAttrGet (row_desc,
OCI_DTYPE_ROW_CHDES, (dvoid *)&row_op,
NULL, OCI_ATTR_CHDES_ROW_OPFLAGS, errhp));
printf ("Row changed is %s row_op %d\n", row_id, row_op);
fflush(stdout);
}
}
void processTableChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp,
OCIColl *table_changes)
{
dvoid **table_descp;
dvoid *table_desc;
dvoid **row_descp;
dvoid *row_desc;
OCIColl *row_changes = (OCIColl *)0;
boolean exist;
ub2 i, j;
dvoid *elemind = (dvoid *)0;
oratext *table_name;
ub4 table_op;
sb4 num_tables;
if (!table_changes) return;
checker(errhp, OCICollSize(envhp, errhp,
(CONST OCIColl *) table_changes, &num_tables));
for (i=0; i<num_tables; i++)
{
checker(errhp, OCICollGetElem(envhp,
errhp, (OCIColl *) table_changes,
i, &exist, &table_descp, &elemind));
table_desc = *table_descp;
checker(errhp, OCIAttrGet (table_desc,
OCI_DTYPE_TABLE_CHDES, (dvoid *)&table_name,
NULL, OCI_ATTR_CHDES_TABLE_NAME, errhp));
checker(errhp, OCIAttrGet (table_desc,
OCI_DTYPE_TABLE_CHDES, (dvoid *)&table_op,
NULL, OCI_ATTR_CHDES_TABLE_OPFLAGS, errhp));
checker(errhp, OCIAttrGet (table_desc,
OCI_DTYPE_TABLE_CHDES, (dvoid *)&row_changes,
NULL, OCI_ATTR_CHDES_TABLE_ROW_CHANGES, errhp));
printf ("Table changed is %s table_op %d\n", table_name,table_op);
fflush(stdout);
if (!bit(table_op, OCI_OPCODE_ALLROWS))
processRowChanges(envhp, errhp, stmthp, row_changes);
}
}
void processQueryChanges(OCIEnv *envhp, OCIError *errhp, OCIStmt *stmthp,
OCIColl *query_changes)
{
sb4 num_queries;
ub8 queryid;
OCINumber qidnum;
ub4 queryop;
dvoid *elemind = (dvoid *)0;
dvoid *query_desc;
dvoid **query_descp;
ub2 i;
boolean exist;
OCIColl *table_changes = (OCIColl *)0;
if (!query_changes) return;
checker(errhp, OCICollSize(envhp, errhp,
(CONST OCIColl *) query_changes, &num_queries));
for (i=0; i < num_queries; i++)
{
checker(errhp, OCICollGetElem(envhp,
errhp, (OCIColl *) query_changes,
i, &exist, &query_descp, &elemind));
query_desc = *query_descp;
checker(errhp, OCIAttrGet (query_desc,
OCI_DTYPE_CQDES, (dvoid *)&queryid,
NULL, OCI_ATTR_CQDES_QUERYID, errhp));
checker(errhp, OCIAttrGet (query_desc,
OCI_DTYPE_CQDES, (dvoid *)&queryop,
NULL, OCI_ATTR_CQDES_OPERATION, errhp));
printf(" Query %d is changed\n", queryid);
if (queryop == OCI_EVENT_DEREG)
printf("Query Deregistered\n");
checker(errhp, OCIAttrGet (query_desc,
OCI_DTYPE_CQDES, (dvoid *)&table_changes,
NULL, OCI_ATTR_CQDES_TABLE_CHANGES, errhp));
processTableChanges(envhp, errhp, stmthp, table_changes);
}
}
void myCallback (ctx, subscrhp, payload, payl, descriptor, mode)
dvoid *ctx;
OCISubscription *subscrhp;
dvoid *payload;
ub4 *payl;
dvoid *descriptor;
ub4 mode;
{
OCIColl *table_changes = (OCIColl *)0;
OCIColl *row_changes = (OCIColl *)0;
dvoid *change_descriptor = descriptor;
ub4 notify_type;
ub2 i, j;
OCIEnv *envhp;
OCIError *errhp;
OCIColl *query_changes = (OCIColl *)0;
OCIServer *srvhp;
OCISvcCtx *svchp;
OCISession *usrhp;
dvoid *tmp;
OCIStmt *stmthp;
(void)OCIEnvInit( (OCIEnv **) &envhp, OCI_DEFAULT, (size_t)0, (dvoid **)0 );
(void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR,
(size_t) 0, (dvoid **) 0);
/* server contexts */
(void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &srvhp, OCI_HTYPE_SERVER,
(size_t) 0, (dvoid **) 0);
(void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &svchp, OCI_HTYPE_SVCCTX,
(size_t) 0, (dvoid **) 0);
OCIAttrGet (change_descriptor, OCI_DTYPE_CHDES, (dvoid *) ¬ify_type,
NULL, OCI_ATTR_CHDES_NFYTYPE, errhp);
fflush(stdout);
if (notify_type == OCI_EVENT_SHUTDOWN ||
notify_type == OCI_EVENT_SHUTDOWN_ANY)
{
printf("SHUTDOWN NOTIFICATION RECEIVED\n");
fflush(stdout);
notifications_processed++;
return;
}
if (notify_type == OCI_EVENT_STARTUP)
{
printf("STARTUP NOTIFICATION RECEIVED\n");
fflush(stdout);
notifications_processed++;
return;
}
notifications_processed++;
checker(errhp, OCIServerAttach( srvhp, errhp, (text *) 0, (sb4) 0,
(ub4) OCI_DEFAULT));
OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &svchp, (ub4) OCI_HTYPE_SVCCTX,
52, (dvoid **) &tmp);
/* set attribute server context in the service context */
OCIAttrSet( (dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX, (dvoid *)srvhp,
(ub4) 0, (ub4) OCI_ATTR_SERVER, (OCIError *) errhp);
/* allocate a user context handle */
OCIHandleAlloc((dvoid *)envhp, (dvoid **)&usrhp, (ub4) OCI_HTYPE_SESSION,
(size_t) 0, (dvoid **) 0);
OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION,
(dvoid *)"HR", (ub4)strlen("HR"), OCI_ATTR_USERNAME, errhp);
OCIAttrSet((dvoid *)usrhp, (ub4)OCI_HTYPE_SESSION,
(dvoid *)"HR", (ub4)strlen("HR"),
OCI_ATTR_PASSWORD, errhp);
checker(errhp, OCISessionBegin (svchp, errhp, usrhp, OCI_CRED_RDBMS,
OCI_DEFAULT));
OCIAttrSet((dvoid *)svchp, (ub4)OCI_HTYPE_SVCCTX,
(dvoid *)usrhp, (ub4)0, OCI_ATTR_SESSION, errhp);
/* Allocate a statement handle */
OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &stmthp,
(ub4) OCI_HTYPE_STMT, 52, (dvoid **) &tmp);
if (notify_type == OCI_EVENT_OBJCHANGE)
{
checker(errhp, OCIAttrGet (change_descriptor,
OCI_DTYPE_CHDES, &table_changes, NULL,
OCI_ATTR_CHDES_TABLE_CHANGES, errhp));
processTableChanges(envhp, errhp, stmthp, table_changes);
}
else if (notify_type == OCI_EVENT_QUERYCHANGE)
{
checker(errhp, OCIAttrGet (change_descriptor,
OCI_DTYPE_CHDES, &query_changes, NULL,
OCI_ATTR_CHDES_QUERIES, errhp));
processQueryChanges(envhp, errhp, stmthp, query_changes);
}
checker(errhp, OCISessionEnd(svchp, errhp, usrhp, OCI_DEFAULT));
checker(errhp, OCIServerDetach(srvhp, errhp, OCI_DEFAULT));
if (stmthp)
OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
if (errhp)
OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR);
if (srvhp)
OCIHandleFree((dvoid *)srvhp, OCI_HTYPE_SERVER);
if (svchp)
OCIHandleFree((dvoid *)svchp, OCI_HTYPE_SVCCTX);
if (usrhp)
OCIHandleFree((dvoid *)usrhp, OCI_HTYPE_SESSION);
if (envhp)
OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV);
}
void registerQuery(svchp, errhp, stmthp, envhp)
OCISvcCtx *svchp;
OCIError *errhp;
OCIStmt *stmthp;
OCIEnv *envhp;
{
OCISubscription *subscrhp;
ub4 namespace = OCI_SUBSCR_NAMESPACE_DBCHANGE;
ub4 timeout = 60;
OCIDefine *defnp1 = (OCIDefine *)0;
OCIDefine *defnp2 = (OCIDefine *)0;
OCIDefine *defnp3 = (OCIDefine *)0;
OCIDefine *defnp4 = (OCIDefine *)0;
OCIDefine *defnp5 = (OCIDefine *)0;
int mgr_id =0;
text query_text1[] = "select last_name, employees.department_id, department_name \
from employees,departments where employee_id = 200 and employees.department_id =\
departments.department_id";
ub4 num_prefetch_rows = 0;
ub4 num_reg_tables;
OCIColl *table_names;
ub2 i;
boolean rowids = TRUE;
ub4 qosflags = OCI_SUBSCR_CQ_QOS_QUERY ;
int empno=0;
OCINumber qidnum;
ub8 qid;
char outstr[MAXSTRLENGTH], dname[MAXSTRLENGTH];
int q3out;
fflush(stdout);
/* allocate subscription handle */
OCIHandleAlloc ((dvoid *) envhp, (dvoid **) &subscrhp,
OCI_HTYPE_SUBSCRIPTION, (size_t) 0, (dvoid **) 0);
/* set the namespace to DBCHANGE */
checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION,
(dvoid *) &namespace, sizeof(ub4),
OCI_ATTR_SUBSCR_NAMESPACE, errhp));
/* Associate a notification callback with the subscription */
checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION,
(void *)myCallback, 0, OCI_ATTR_SUBSCR_CALLBACK, errhp));
/* Allow extraction of rowid information */
checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION,
(dvoid *)&rowids, sizeof(ub4),
OCI_ATTR_CHNF_ROWIDS, errhp));
checker(errhp, OCIAttrSet (subscrhp, OCI_HTYPE_SUBSCRIPTION,
(dvoid *)&qosflags, sizeof(ub4),
OCI_ATTR_SUBSCR_CQ_QOSFLAGS, errhp));
/* Create a new registration in the DBCHANGE namespace */
checker(errhp,
OCISubscriptionRegister(svchp, &subscrhp, 1, errhp, OCI_DEFAULT));
/* Multiple queries can now be associated with the subscription */
subhandle1 = subscrhp;
printf("Registering query : %s\n", (const signed char *)query_text1);
/* Prepare the statement */
checker(errhp, OCIStmtPrepare (stmthp, errhp, query_text1,
(ub4)strlen((const signed char *)query_text1), OCI_V7_SYNTAX,
OCI_DEFAULT));
checker(errhp,
OCIDefineByPos(stmthp, &defnp1,
errhp, 1, (dvoid *)outstr, MAXSTRLENGTH * sizeof(char),
SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT));
checker(errhp,
OCIDefineByPos(stmthp, &defnp2,
errhp, 2, (dvoid *)&empno, sizeof(empno),
SQLT_INT, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT));
checker(errhp,
OCIDefineByPos(stmthp, &defnp3,
errhp, 3, (dvoid *)&dname, sizeof(dname),
SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT));
/* Associate the statement with the subscription handle */
OCIAttrSet (stmthp, OCI_HTYPE_STMT, subscrhp, 0,
OCI_ATTR_CHNF_REGHANDLE, errhp);
/* Execute the statement, the execution performs object registration */
checker(errhp, OCIStmtExecute (svchp, stmthp, errhp, (ub4) 1, (ub4) 0,
(CONST OCISnapshot *) NULL, (OCISnapshot *) NULL ,
OCI_DEFAULT));
fflush(stdout);
OCIAttrGet(stmthp, OCI_HTYPE_STMT, &qid, (ub4 *)0,
OCI_ATTR_CQ_QUERYID, errhp);
printf("Query Id %d\n", qid);
/* commit */
checker(errhp, OCITransCommit(svchp, errhp, (ub4) 0));
}
static void cleanup(envhp, svchp, srvhp, errhp, usrhp)
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIServer *srvhp;
OCIError *errhp;
OCISession *usrhp;
{
/* detach from the server */
checker(errhp, OCISessionEnd(svchp, errhp, usrhp, OCI_DEFAULT));
checker(errhp, OCIServerDetach(srvhp, errhp, (ub4)OCI_DEFAULT));
if (usrhp)
(void) OCIHandleFree((dvoid *) usrhp, (ub4) OCI_HTYPE_SESSION);
if (svchp)
(void) OCIHandleFree((dvoid *) svchp, (ub4) OCI_HTYPE_SVCCTX);
if (srvhp)
(void) OCIHandleFree((dvoid *) srvhp, (ub4) OCI_HTYPE_SERVER);
if (errhp)
(void) OCIHandleFree((dvoid *) errhp, (ub4) OCI_HTYPE_ERROR);
if (envhp)
(void) OCIHandleFree((dvoid *) envhp, (ub4) OCI_HTYPE_ENV);
}
18.9 CQN登録の問合せ
登録のQOSオプションなど、すべての登録に関するトップレベルの情報を確認するには、静的データ・ディクショナリ・ビュー*_CHANGE_NOTIFICATION_REGS
を問い合せます。
たとえば、クライアントの登録IDと通知を受信するオブジェクトのリストを取得できます。HR
の登録IDと表名を表示するには、次の問合せを使用します。
SELECT regid, table_name FROM USER_CHANGE_NOTIFICATION_REGS;
どの問合せがQRCNに対して登録されているかを確認するには、静的データ・ディクショナリ・ビューUSER_CQ_NOTIFICATION_QUERIES
またはDBA_CQ_NOTIFICATION_QUERIES
を問い合せます。これらのビューには、問合せで使用されるバインド値に関する情報が含まれています。これらのビューでは、元の問合せのバインド値が、定数として問合せテキストに含まれます。問合せテキストは、登録済の元の問合せと同等ですが、同一ではない場合があります。
関連項目:
静的データ・ディクショナリ・ビューUSER_CHANGE_NOTIFICATION_REGS
およびDBA_CQ_NOTIFICATION_QUERIES
の詳細は、『Oracle Databaseリファレンス』を参照してください。
18.10 通知の解析
トランザクションのコミット時に、データベースでは登録済オブジェクトがトランザクション中に変更されたかどうかが判別されます。変更されたと判別された場合、登録で指定されている通知ハンドラが実行されます。
トピック:
18.10.1 CQ_NOTIFICATION$_DESCRIPTORオブジェクトの解析
CQN登録で通知が生成されると、CQ_NOTIFICATION$_DESCRIPTOR
オブジェクトがデータベースから通知ハンドラに渡されます。通知ハンドラでは、データベース変更の詳細をCQ_NOTIFICATION$_DESCRIPTOR
オブジェクトの属性から検出できます。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_DESCRIPTOR
表18-4に、CQ_NOTIFICATION$_DESCRIPTOR
の属性の要約を示します。
表18-4 CQ_NOTIFICATION$_DESCRIPTORの属性
属性 | 説明: |
---|---|
|
登録時に戻された登録ID。 |
|
変更を行ったトランザクションのID。 |
|
通知が生成されたデータベースの名前。 |
|
通知をトリガーするデータベース・イベント。たとえば、属性には様々なデータベース・イベントに対応する次の定数を含めることができます。
|
|
変更された表の数。 |
|
このフィールドは、OCN登録の場合にのみ存在します。QRCN登録の場合は
前述以外の場合は |
|
このフィールドは、QRCN登録の場合にのみ存在します。OCN登録の場合は
前述以外の場合は |
18.10.2 CQ_NOTIFICATION$_TABLEオブジェクトの解析
CQ_NOTIFICATION$_DESCRIPTOR
タイプには、タイプCQ_NOTIFICATION$_TABLE
の表記述子のVARRAY
を保持する属性TABLE_DESC_ARRAY
が含まれています。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_TABLE
表18-5に、CQ_NOTIFICATION$_TABLE
の属性の要約を示します。
表18-5 CQ_NOTIFICATION$_TABLEの属性
属性 | 指定内容 |
---|---|
|
変更があった表に対して実行された操作のタイプ。たとえば、属性には様々なデータベース操作に対応する次の定数を含めることができます。
|
|
変更があった表の名前。 |
|
変更があった行数。 |
|
タイプ |
18.10.3 CQ_NOTIFICATION$_QUERYオブジェクトの解析
CQ_NOTIFICATION$_DESCRIPTOR
タイプには、タイプCQ_NOTIFICATION$_QUERY
の結果セット変更記述子のVARRAY
を保持する属性QUERY_DESC_ARRAY
が含まれています。
SQL*Plusでは、SYS
として接続し、次の文を実行するとこれらの属性を表示できます。
DESC CQ_NOTIFICATION$_QUERY
表18-6に、CQ_NOTIFICATION$_QUERY
の属性の要約を示します。
表18-6 CQ_NOTIFICATION$_QUERYの属性
属性 | 指定内容 |
---|---|
|
変更された問合せのID。 |
|
問合せを変更した操作( |
|
タイプ |
18.10.4 CQ_NOTIFICATION$_ROWオブジェクトの解析
登録中にROWID
オプションが指定されると、CQ_NOTIFICATION$_TABLE
タイプにはROW_DESC_ARRAY
属性、つまり、変更された行のROWID
を含むタイプCQ_NOTIFICATION$_ROW
のVARRAY
が設定されます。CQ_NOTIFICATION$_TABLE
オブジェクトのOPFLAGS
フィールドにALL_ROWS
が設定されていた場合、ROWID
情報は使用できません。
表18-7に、CQ_NOTIFICATION$_ROW
の属性の要約を示します。
表18-7 CQ_NOTIFICATION$_ROWの属性
属性 | 指定内容 |
---|---|
|
変更があった表に対して実行された操作のタイプ。 |
|
変更された行の |