TimesTenトランザクション・ログAPI(XLA)はC言語関数のセットで、この関数によって、次の処理を実行するアプリケーションを実装できます。
ローカル・データベースの指定した表への変更に関してTimesTenを監視するアプリケーション。
これらの変更の通知をリアルタイムで受信するアプリケーション。
XLAの主な目的は、トリガーにかわる高パフォーマンスで非同期の機能です。
|
注意: 『Oracle TimesTen In-Memory Database開発者および管理者ガイド』で説明するTimesTenレプリケーション・ソリューションではニーズが満たされない場合、XLA関数を使用してカスタム・データ・レプリケーション・ソリューションを構築できます。 |
この章の内容は次のとおりです。
各XLA関数の詳細は、第9章「XLAリファレンス」を参照してください。
この項の内容は次のとおりです。
ここで説明するXLA関数については、第9章「XLAリファレンス」を参照してください。
TimesTen XLAによって更新レコードがトランザクション・ログ・バッファまたはトランザクション・ログ・ファイルから直接取得されるため、それらの更新レコードは、必要とされるかぎり使用可能です。また、ロギング・モデルによって、複数のリーダーでトランザクション・ログの更新を同時に読み取ることができます。
ttXlaPersistOpen XLA関数は、でデータベースへの接続をオープンします。
TimesTenは、初期作成時に、アプリケーションがリンクされているTimesTenリリースと同じバージョンに、トランザクション・ログ・ハンドルを構成します。また、ttXlaGetVersionおよびttXlaSetVersion XLA関数を使用して、以前のXLAバージョンと相互運用することもできます。
アプリケーションによってデータベースが変更されると、TimesTenは、データの変更およびトランザクション・コミットなどの他のイベントを示すトランザクション・ログ・レコードを生成します。
新しいトランザクション・ログ・レコードは、生成されると常にログ・バッファの最後に書き込まれます。
トランザクション・ログ・レコードは、メモリ内のログ・バッファからディスク上のトランザクション・ログ・ファイルへ定期的に一括でフラッシュされます。XLAが初期化されている場合、XLAアプリケーションでは、トランザクション・ログのどの部分がディスクまたはメモリーに存在するかについて考慮する必要はありません。したがって、この章で使用するトランザクション・ログという用語は、トランザクション更新レコードの仮想ソースを指し、これらのレコードが物理的にメモリーまたはディスクに保存されているかどうかは関係ありません。
アプリケーションで、XLAを使用し、データベースへの変更に関してトランザクション・ログを監視できます。XLAは、トランザクション・ログ全体を読み取り、ログ・レコードをフィルタ処理し、目的の表および列への変更が含まれているトランザクション・レコードのリストをXLAアプリケーションに配信します。
XLAは、レコードをディスクリート・トランザクションにソートします。複数のアプリケーションが同時にデータベースを更新している場合、異なるアプリケーションのトランザクション・ログ・レコードがトランザクション・ログに交互配置されます。
XLAは、特定のトランザクションに関連するすべてのトランザクション・ログ・レコードを透過的に抽出し、それらを連続するリストにしてアプリケーションに配信します。
コミット済のトランザクションのレコードのみが返されます。コミット済のレコードは、最後のコミット・レコードがトランザクション・ログに書き込まれる順序で返されます。コミットされていないデータベースへの変更に関連するレコードは、XLAによってフィルタ処理されます。
変更が行われ、その後にロールバックされた場合、XLAは強制終了されたトランザクションのレコードをアプリケーションに配信しません。
これらのXLAの基本概念のほぼすべてを次の例5-1に示し、次にそれぞれの内容を箇条書きで示します。
図5-1に示すトランザクション・ログを例として考えてみます。
例5-1 トランザクション・ログ・レコードの読取り
この例では、トランザクション・ログに次のレコードが含まれています。
CT1 - アプリケーションCは、表Wの行1を値7.7で更新。BT1 - アプリケーションBは、表Xの行3を値2で更新。CT2 - アプリケーションCは、表Wの行9を値5.6で更新。BT2 - アプリケーションBは、表Yの行2を値XYZで更新。AT1 - アプリケーションAは、表Zの行1を値3で更新。AT2 - アプリケーションAは、表Zの行3を値4で更新。BT3 - アプリケーションBがトランザクションをコミット。AT3 - アプリケーションAがトランザクションをロールバック。CT3 - アプリケーションCがトランザクションをコミット。表W、YおよびZへの変更を検出するように設定されているXLAアプリケーションでは次のようになります。
BT2およびBT3 - 表Yの行2を値XYZで更新し、コミット。CT1 - 表Wの行1を値7.7で更新。CT2およびCT3 - 表Wの行9を値5.6で更新し、コミット。この例では、次のことが示されています。
アプリケーションBおよびCのすべてのトランザクション・レコードが同時に書き込まれます。
トランザクション・ログでは、アプリケーションCのレコードがアプリケーションBのレコードより前に書き込まれますが、アプリケーションBのコミット(BT3)はアプリケーションCのコミット(CT3)より前に書き込まれます。その結果、アプリケーションBのレコードはアプリケーションCのレコードより先にXLAアプリケーションに返されます。
XLAは表Xへの変更を検出するように設定されていないため、アプリケーションBによる表Xへの更新(BT1)は示されません。
アプリケーションAによる表Zへの更新(AT1およびAT2)が示されないのは、コミットされずにロールバックされたためです(AT3)。
XLAを使用すると、表およびマテリアライズド・ビューの両方への変更を追跡できます。マテリアライズド・ビューによって、複数のディテール表内の選択した行および列への変更を追跡できる単一のソースが提供されます。マテリアライズド・ビューがない場合は、XLAアプリケーションで、すべてのディテール表の更新レコード(アプリケーションにとって重要ではない行および列への更新が反映されているレコードを含む)に対して監視およびフィルタ処理を行う必要があります。
一般に、表またはマテリアライズド・ビューへの変更を追跡するために使用するXLAメカニズムの間に処理上の違いはありません。ただし、非同期のマテリアライズド・ビューの場合、非同期のマテリアライズド・ビューのXLA通知の順序は、関連付けられているディテール表での順序または同期のマテリアライズド・ビューでの順序と同じとはかぎらないことに注意してください。たとえば、ディテール表に2つの挿入が行われる場合、非同期のマテリアライズド・ビューでは逆の順序で挿入が行われることがあります。さらに、マテリアライズド・ビューのディテール表への更新は、削除とその後に続く挿入としてXLAによって扱われることがあります。また、複数の挿入や複数の削除などの複数の操作を組み合せて1つの操作にすることもできます。正確な順序に依存するアプリケーションでは、非同期のマテリアライズド・ビューを使用しないでください。
マテリアライズド・ビューの詳細は、次を参照してください。
『Oracle TimesTen In-Memory Database SQLリファレンス・ガイド』のCREATE MATERIALIZED VIEWに関する項
『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』のマテリアライズド・ビューの理解に関する項
各XLAリーダーは、ブックマークを使用して更新ログ・ストリーム内での位置を維持します。各ブックマークは、ログ・レコード識別子を使用してトランザクション・ログ内の更新レコードを追跡する2つのポインタで構成されています。
初期読取りログ・レコード識別子は、最後に確認されたトランザクション・ログ・レコードを指します。初期読取りログ・レコード識別子はデータベースに保存されるため、データベースの接続、停止および障害が発生しても永続的に維持されます。
現行読取りログ・レコード識別子は、トランザクション・ログから現在読み取られているレコードを指します。
この項の以降の内容は次のとおりです。
「XLAの初期化およびXLAハンドルの取得」で説明するとおり、ttXlaPersistOpen関数をコールしてXLAハンドルを初期化する場合、tagパラメータを使用して新しいブックマークまたはシステムに既存のブックマークのいずれかを指定し、optionsパラメータを使用してそのブックマークが新しいレプリケートされていないブックマーク、新しいレプリケートされたブックマーク、または既存(再利用)のブックマークのいずれであるかを指定します。この時点で、ブックマークに関連付けられている初期読取りログ・レコード識別子は、データベースから読み取られ、XLAハンドル(ttXlaHandle_h)にキャッシュされます。この識別子は、トランザクション・ログ内のリーダーの開始位置を示します。
詳細は、『Oracle TimesTen In-Memory Databaseリファレンス』のttLogHoldsに関する説明を参照してください。TimesTen組込みプロシージャは、トランザクション・ログの保留に関する情報を返します。
アプリケーションが最初にXLAを初期化し、XLAハンドルを取得した時点では、現行読取りログ・レコード識別子と初期読取りログ・レコード識別子は、この後の図5-2に示すように、両方ともデータベースに最後に書き込まれたレコードを指しています。
ttXlaNextUpdateまたはttXlaNextUpdateWait関数を使用すると、一連のコミット済トランザクションのレコードが、コミットされた順序でトランザクション・ログから返されます(「トランザクション・ログからの更新レコードの取得」を参照)。図5-3に示すように、ttXlaNextUpdateをコールするたびに、ブックマークの現行読取りログ・レコード識別子は、最後に読み取られたレコードに再設定されます。現行読取りログ・レコード識別子によって、次にttXlaNextUpdateをコールする場合の開始位置がマークされます。
ttXlaGetLSNおよびttXlaSetLSN関数を使用すると、レコードを再度読み取ることができます(「ブックマークの位置の変更」を参照)。ただし、図5-4に示すように、ttXlaAcknowledge関数をコールすると、ブックマークの初期読取りログ・レコード識別子が現行読取りログ・レコード識別子に永続的に再設定されます。ttXlaAcknowledge関数をコールして初期読取りログ・レコード識別子を再設定すると、以前に読み取られたすべてのトランザクション・レコードにパージのフラグが設定されます。初期読取りログ・レコード識別子を再設定すると、ttXlaSetLSN関数を使用して元に戻し、以前に読み取ったトランザクションを再度読み取ることはできません。
|
注意: ttXlaAcknowledgeをコールすると、確認する関連更新レコードがない場合でもブックマークが再設定されます。これは、トランザクション・ログ領域の管理に役立ちますが、処理コストとのバランスを保つ必要があります。XLAでは、トランザクション・ログが一度に1ファイルずつパージされる点に注意してください。処理の詳細は、「ttXlaAcknowledge」を参照してください。 |
データベースで作成されるブックマークの数は64に制限されています。各ブックマークに一度に関連付けることができるアクティブな接続は1つのみです。ただし、ブックマークは、存続期間中に多数の接続と関連付けることができます。アプリケーションによって、接続をオープンし、新しいブックマークを作成し、そのブックマークを接続と関連付け、ブックマークを使用してレコードを読み取り、接続をデータベースから切断し、データベースに再接続して、新しい接続を作成し、この新しい接続を既存のブックマークと関連付け、以前の接続が停止されたトランザクション・ログ・レコードから読取りを続行することができます。
アクティブ・スタンバイ・ペアのレプリケーション・スキームを使用する場合は、ttXlaPersistOpenコールのoptionsの設定に従ってレプリケートされたブックマークを使用できます。レプリケートされたブックマークの場合、ブックマークに対する処理は必要に応じてスタンバイ・データベースにレプリケートされます。これにより、フェイルオーバーが発生した場合、より効率的にブックマークの位置をリカバリできます。読取りは、新しいアクティブ・ストアへのスイッチオーバー前に読取りが中断された位置に近いXLAレコードのストリームから再開されます。レプリケートされたブックマークがない場合、古いアクティブ・ストアに返されている多数の重複レコードを読み取る必要があります。
レプリケートされたブックマークを使用する際には、次の順に手順を実行する必要があります。
アクティブ・スタンバイ・ペアのレプリケーション・スキームを作成します。(これは、create active standby pair操作、またはクラスタウェア管理環境のttCWAdmin -createコマンドによって行われます。)
ブックマークを作成します。
ブックマークをサブスクライブします。
アクティブ・スタンバイ・ペアを起動します(このとき、スタンバイへの複製が発生し、レプリケーションが始まります)。(これはttRepAdmin -duplicateコマンド、またはクラスタウェア管理環境のttCWAdmin -startコマンドによって行われます。)
使用に関しては、次の点に注意してください。
スタンバイ・データベースでのブックマークの位置は、アクティブ・データベースでのブックマークの位置に非常に近くなります。ただし、確認処理のレプリケーションは非同期に行われるため、確認処理の実行頻度によっては、フェイルオーバーの発生時に更新の重複を示す小さなウィンドウが表示されることがあります。
データベースがスタンバイ・ステータスからアクティブ・ステータスに変わった後に、ttXlaClose関数とttXlaPersistOpen関数を使用して、そのデータベース上のすべてのブックマークを閉じ、再度開く必要があります。ブックマークは、レプリケーション・エージェントによってスタンバイ・データベース上で必要に応じて自動的に再配置されるため、スタンバイ・データベース上のレプリケートされたブックマークの状態は、通常のXLA処理中に変化します。データベースがアクティブ・ステータスに変更される前に開いていたブックマークを使用しようとすると、ブックマークの状態がリセットされたこととブックマークが再配置されたことを示すエラーが表示されます。この場合でも再配置されたブックマークから読取りを続行できますが、ブックマークを閉じて再度開くと、エラーを回避できます。
レプリケートされたブックマークが存在する間に、アクティブ・スタンバイ・ペアのスキームを削除できます。ブックマークは当然その時点でレプリケートされなくなりますが、削除はされません。続けてアクティブ・スタンバイ・ペア・スキームを再び有効にすると、これらのブックマークは自動的にスキームに追加されます。
レプリケーション・エージェントの実行中は、レプリケートされたブックマークを削除できません。
アクティブ・データベース内のレプリケートされたブックマークのみを読み取って確認できます。レプリケートされたブックマークを確認するたびに、確認処理がスタンバイ・データベースに非同期にレプリケートされます。
XLAが使用中の場合、TimesTenのトランザクション・ログ・ファイルは、XLAのブックマークが進むまで保留されることに注意してください。保留によって、XLAで必要でないことが確認されるまで、トランザクション・ログ・ファイルはパージされません。XLAアプリケーションが予期せず終了したり、そのブックマークの削除や変更トラッキングの無効化を行わずに切断した場合などに、ブックマークが固まってしまうと、ログは保持されたままになり、トランザクション・ログ・ファイルが過度に蓄積される場合があります。この蓄積の結果、ディスク領域が一杯になる場合があります。
この状況を監視し、対処するための情報は、『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』のトランザクション・ログ・ファイルの蓄積の監視に関する説明を参照してください。
表5-1に、内部SQLデータ型と、リリース7.0より前およびリリース7.0以上のXLAデータ型のマッピングを示します。TimesTenデータ型の詳細は、『Oracle TimesTen In-Memory Database SQLリファレンス・ガイド』のデータ型に関する項を参照してください。
表5-1 XLAデータ型のマッピング
| 内部SQLデータ型 | リリース7.0より前のXLAデータ型 | リリース7.0以上のXLAデータ型 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- |
|
|
|
- |
|
|
|
- |
|
|
|
- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- |
|
|
|
- |
|
|
|
- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- |
|
|
|
- |
|
|
|
|
|
|
|
|
|
|
|
- |
|
|
|
- |
|
|
|
- |
|
|
|
- |
|
XLAでは、内部SQLデータ型と外部プログラム・データ型を変換する関数が提供されます。たとえば、ttXlaNumberToCStringを使用すると、NUMBER列を文字列に変換できます。TimesTenは、次のXLAデータ型変換関数を備えています。
データベース内の処理に与えるTimesTenアクセス制御の影響の概要については、「アクセス制御に関するTimesTen機能の考慮事項」を参照してください。アクセス制御は、次のようにXLAに影響を与えます。
次のようなXLA機能の実行には、システム権限XLAが必要です。
ttXlaPersistOpen C関数を使用するなど、XLAリーダーとしてのTimesTenへの接続(CREATE SESSION権限も必要)
その他のXLA関連のTimesTen C関数の実行(第9章「XLAリファレンス」を参照)
XLA関連のTimesTen組込みプロシージャの実行
プロシージャttXlaBookmarkCreate、ttXlaBookmarkDelete、ttXlaSubscribeおよびttXlaUnsubscribeについては、『Oracle TimesTen In-Memory Databaseリファレンス』の組込みプロシージャに関する説明を参照してください。
XLA権限を持つユーザーには、SELECT ANY TABLE、SELECT ANY VIEWおよびSELECT ANY SEQUENCEシステム権限と同等の権限があり、データベースで発生するDDL文レコードを取得できます。その結果、このユーザーは、XLA権限を持たない場合にはアクセスが許可されていないデータベース・オブジェクトに関する情報を取得できる点に注意してください。
TimesTen XLAの使用時は、次の制限事項に注意してください。
XLAは、TimesTenでサポートされているすべてのプラットフォームで使用できます。ただし、異なるプラットフォーム間、または同じプラットフォームの32ビットと64ビットとの間でのデータの転送はXLAではサポートされていません。
LOBに対するXLAのサポートは限定されています。詳細は、「更新を監視する表の指定」を参照してください。
XLAでは、ドライバ・マネージャ・ライブラリまたはクライアント/サーバー・ライブラリにリンクされたアプリケーションはサポートされていません。(クイック・スタート・アプリケーションで提供されるTimesTenドライバ・マネージャではXLAをサポートしていますが、ドライバ・マネージャ自体が完全にサポートされているわけではありません。「ODBCドライバ・マネージャとのリンク」のこのドライバ・マネージャについての注意を参照してください。)
XLAリーダーは、インメモリー列圧縮を使用する表をサブスクライブできません。
Oracle Databaseに対する変更トラッキング・トリガーでは、自動リフレッシュ・キャッシュ・グループの列レベルでの解決は行いません。(これには非常に費用がかかります。)そのため、自動リフレッシュ機能では、実際にはすべての列のデータが変更されていない場合でも、行のすべての列が更新され、XLAではすべての列が変更されたということのみがレポートされます。
TimesTenには、この章で説明する多数のXLA関数の使用方法を示すxlaSimpleデモが用意されています。このデモはquickstart/sample_code/odbc/xlaディレクトリにあります。
C開発者向けのTimesTenデモ・プログラムの概要は、「TimesTen Cのデモについて」を参照してください。詳細は、install_dir/quickstart.htmlを参照してください。odbcディレクトリのREADMEファイルには、xlaSimpleのビルドおよび実行の手順が記載されています。
次の項「XLAイベント・ハンドラ・アプリケーションの作成」に示すサンプル・コードなど、この章の大部分の内容は、このxlaSimpleデモに基づいています。このデモでは、表MYDATAがAPPUSERスキーマに作成されています。APPUSERとしてログインしている間は、その表を更新することになります。XLAUSERとしてログインしている間は、xlaSimpleデモが更新についてレポートします。
デモを実行するには、コマンド・プロンプトでxlaSimpleを実行します。サンプル・データベースの作成時に指定したXLAUSERのパスワードを入力するように求められます。別のコマンド・プロンプトでttIsqlを開始して、APPUSERとしてTimesTenサンプル・データベースに接続します。再度、サンプル・データベースの作成時に指定したパスワードの入力を求められます。
ttIsqlコマンド・プロンプトで、DML文を入力して表を変更できます。その後、xlaSimpleウィンドウでXLA出力を表示できます。
この項では、データベース内の選択した表への変更を検出およびレポートするXLAアプリケーションの一般的な作成手順について説明します。「列データの確認」のような例外もありますが、この項で説明する手順は、ほぼすべてのXLAアプリケーションに適用できます。
この項で説明する手順は次のとおりです。
この項のコード例は、xlaSimpleデモ・アプリケーションに基づいています。
ここで説明するXLA関数については、第9章「XLAリファレンス」を参照してください。
すべてのODBCアプリケーションと同様に、XLAアプリケーションは、ODBCを初期化し、環境ハンドル(henv)を取得し、接続ハンドル(hdbc)を取得して、特定のデータベースと通信する必要があります。
環境ハンドルおよび接続ハンドルを初期化します。
SQLHENV henv = SQL_NULL_HENV; SQLHDBC hdbc = SQL_NULL_HDBC;
henvのアドレスをSQLAllocEnv ODBC関数に渡して環境ハンドルを割り当てます。
rc = SQLAllocEnv(&henv);
hdbcのアドレスをSQLAllocConnect ODBC関数に渡して、データベース用の接続ハンドルを割り当てます。
rc = SQLAllocConnect(henv, &hdbc);
SQLDriverConnect ODBC関数をコールして、接続文字列(connStr)で指定されたデータベースに接続しますが、この接続文字列は、この例ではコマンドラインから渡されます。
static char connstr[CONN_STR_LEN];
...
rc = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connstr, SQL_NTS, NULL, 0,
NULL, SQL_DRIVER_COMPLETE);
|
注意: XLAアプリケーションで使用するためにODBC接続ハンドルをオープンすると、ttXlaCloseをコールして対応するXLAハンドルをクローズするまで、ODBCハンドルをODBC処理に使用できません。 |
SQLSetConnectOption ODBC関数をコールして、自動コミットをオフにします。
rc = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
前述の項「データベース接続ハンドルの取得」の説明に従ってODBCを初期化し、環境ハンドルおよび接続ハンドルを取得した後、XLAを初期化し、XLAハンドルを取得して、トランザクション・ログにアクセスできます。1つのODBC接続に対して1つのみのXLAハンドルを作成します。アプリケーションで複数のXLAリーダー・スレッドを使用する(それぞれが独自のXLAブックマークに接続されている)場合は、スレッドごとに個別のXLAハンドルおよびODBC接続を作成します。
この項では、XLAを初期化する方法について説明します。XLAを初期化する前に、ブックマークを初期化します。その後、XLAハンドルをttXlaHandle_h型として初期化します。
unsigned char bookmarkName [32]; ... strcpy((char*)bookmarkName, "xlaSimple"); ... ttXlaHandle_h xla_handle = NULL;
bookmarkNameおよびxla_handleのアドレスをttXlaPersistOpen関数に渡してXLAハンドルを取得します。
rc = ttXlaPersistOpen(hdbc, bookmarkName, XLACREAT, &xla_handle);
XLACREATオプションは、新しいレプリケートされていないブックマークの作成に使用します。または、XLAREPLオプションを使用して、レプリケートされたブックマークを作成します。どちらの場合も、ブックマークがすでに存在する場合は処理に失敗します。
すでに存在するブックマークを使用するには、次の例で示すように、XLAREUSEオプションを指定してttXlaPersistOpenをコールします。
#include <tt_errCode.h> /* TimesTen Native Error codes */
...
if ( native_error == 907 ) { /* tt_ErrKeyExists */
rc = ttXlaPersistOpen(hdbc, bookmarkName, XLAREUSE, &xla_handle);
...
}
ttXlaPersistOpenに無効なパラメータが指定されている場合、またはアプリケーションでハンドル用のメモリーを割り当てることができなかった場合は、SQL_INVALID_HANDLEというコードが返されます。この場合、このエラーまたはこれ以降のエラーの検出にttXlaErrorは使用できません。
ttXlaPersistOpenが失敗しても、まだハンドルを作成する場合は、メモリー・リークを防ぐためにハンドルを閉じる必要があります。
前述の項「XLAの初期化およびXLAハンドルの取得」の説明に従ってXLAを初期化し、XLAハンドルを取得した後、更新イベントを監視する表またはマテリアライズド・ビューを指定できます。
SYS.XLASUBSCRIPTIONS表を問い合せることによって、ブックマークをサブスクライブする表を確認できます。また、SYS.XLASUBSCRIPTIONSを使用すると、特定の表にサブスクライブしたブックマークを確認することもできます。
ttXlaNextUpdateおよびttXlaNextUpdateWait関数は、DDLイベントに関連するXLAレコードを取得します。DDL XLAレコードはどのXLAブックマークでも使用できます。DDLイベントには、CREATAB、DROPTAB、CREAIND、DROPIND、CREATVIEW、DROPVIEW、CREATSEQ、DROPSEQ、CREATSYN、DROPSYN、ADDCOLS、DRPCOLSおよびTRUNCATEトランザクションが含まれています。これらのイベント・タイプの詳細は、「ttXlaUpdateDesc_t」を参照してください。
ttXlaTableStatus関数は、現行のブックマークを指定した表に更新するためにサブスクライブします。または、現在のブックマークが表に関連するDMLレコードをすでに監視しているかどうかを判別します。
ttXlaTableByName関数をコールして、指定した表またはマテリアライズド・ビューのシステム識別子およびユーザー識別子の両方を取得します。次に、ttXlaTableStatus関数をコールして、表またはマテリアライズド・ビューへの変更を監視するためにXLAを有効にします。
XLAでのLOBのサポートは、次のように制限されています。
|
例5-2 更新を監視する表の指定
この例では、MYDATA表への変更を追跡します。
#define TABLE_OWNER "APPUSER"
#define TABLE_NAME "MYDATA"
...
SQLUBIGINT SYSTEM_TABLE_ID = 0;
...
SQLUBIGINT userID;
rc = ttXlaTableByName(xla_handle, TABLE_OWNER, TABLE_NAME,
&SYSTEM_TABLE_ID, &userID);
表の識別子を取得すると、ttXlaTableStatus関数を使用して、MYDATA表への変更を検出するためのXLA更新追跡を有効にできます。newstatusパラメータを0(ゼロ)以外の値に設定すると、指定した表に行われた変更がXLAによって追跡されます。
SQLINTEGER oldstatus;
SQLINTEGER newstatus = 1;
...
rc = ttXlaTableStatus(xla_handle, SYSTEM_TABLE_ID, 0,
&oldstatus, &newstatus);
oldstatusパラメータは、コール時の表のステータスを示す出力です。
ttXlaTableStatusでnewstatusをNULLにし、oldstatusのみを返すことによって、表の現行のXLAステータスを返すことができます。次に例を示します。
rc = ttXlaTableStatus(xla_handle, SYSTEM_TABLE_ID, 0,
&oldstatus, NULL);
...
if (oldstatus != 0)
printf("XLA is currently tracking changes to table %s.%s\n",
TABLE_OWNER, TABLE_NAME);
else
printf("XLA is not tracking changes to table %s.%s\n",
TABLE_OWNER, TABLE_NAME);
更新を監視する表を指定した後、ttXlaNextUpdateまたはttXlaNextUpdateWait関数をコールして、トランザクション・ログから一連のレコードを返すことができます。コミット済のトランザクションのレコードのみが返されます。レコードはコミットされた順序で返されます。不要になり、トランザクション・ログからパージ可能なレコードをXLAが判別できるように、ttXlaAcknowledge関数を定期的にコールして、トランザクションの受信を確認する必要があります。これらの関数は、トランザクション・ログ内のアプリケーションのブックマークの位置に影響を与えます(「ブックマークの動作」を参照)。詳細は、『Oracle TimesTen In-Memory Databaseリファレンス』のttLogHoldsに関する説明も参照してください。TimesTen組込みプロシージャは、トランザクション・ログの保留に関する情報を返します。
|
注意: ttXlaAcknowledgeは、必要な場合にのみ使用する必要がある高コストの処理です。 |
ttXlaNextUpdateによって返されたトランザクション内の各更新レコードは、ttXlaUpdateDesc_t構造体で記述される更新ヘッダーで始まります。この更新ヘッダーには、レコードがトランザクションの最初のレコード(TT_UPDFIRST)か、最後のコミット・レコード(TT_UPDCOMMIT)かを示すフラグが格納されています。また、更新ヘッダーは、更新によって影響を受ける表も識別します。更新ヘッダーの後には、データベース内の表に対して行われた変更について記述する0(ゼロ)から2行の行データが続きます。
次の図5-5に、4つの更新レコードで構成されているトランザクションをトランザクション・ログから返すttXlaNextUpdateへのコールを示します。返されたトランザクションの受信は、ttXlaAcknowledgeをコールすることで確認され、ブックマークが再設定されます。
|
注意: この例は、説明を明確にするために簡略化されています。実際のXLAアプリケーションでは、ttXlaAcknowledgeをコールする前に、複数のトランザクションのレコードを読み取る可能性があります。 |
例5-3 トランザクション・ログからの更新レコードの取得
xlaSimpleデモでは、表の更新に対する監視は、ユーザーが停止するまで続行されます。
この例では、ttXlaNextUpdateWaitをコールする前に、返されるttXlaUpdateDesc_tレコードを保持するバッファへのポインタ(arry)と実際に返されるレコード数を保持する変数(records)を初期化します。この例ではttXlaNextUpdateWaitをコールするため、トランザクション・ログ・バッファでレコードが見つからない場合に待機する秒数(FETCH_WAIT_SECS)も指定します。
次に、ttXlaNextUpdateWaitをコールし、これらの値を渡してarry内の一連のttXlaUpdateDesc_tレコードを取得します。例5-4に示すように、arry内の各レコードは、HandleChange()関数に渡すことによって処理されます。すべてのレコードの処理後、ttXlaAcknowledgeをコールして、ブックマークの位置を再設定します。
#define FETCH_WAIT_SECS 5
...
SQLINTEGER records;
ttXlaUpdateDesc_t** arry;
int j;
while (!StopRequested()) {
/* Get a batch of update records */
rc = ttXlaNextUpdateWait(xla_handle, &arry, 100,
&records, FETCH_WAIT_SECS);
if (rc != SQL_SUCCESS {
/* See "Handling XLA errors" */
}
/* Process the records */
for(j=0; j < records; j++){
ttXlaUpdateDesc_t* p;
p = arry[j];
HandleChange(p); /* Described in the next section */
}
/* After each batch, Acknowledge updates to reset bookmark.*/
rc = ttXlaAcknowledge(xla_handle);
if (rc != SQL_SUCCESS {
/* See "Handling XLA errors" */
}
} /* end while !StopRequested() */
ttXlaNextUpdateまたはttXlaNextUpdateWaitによって返されるレコードの実際の数は、これらの関数のnreturned出力パラメータで示されるように、maxrecordsパラメータの値より小さい場合があります。図5-6に示す例では、maxrecordsが10で、7つのレコードで構成されているトランザクションATおよび3つのレコードで構成されているトランザクションBTがトランザクション・ログに含まれています。この場合、両方のトランザクションが同じバッチで返され、maxrecordsおよびnreturnedの両方の値が10になります。ただし、ログ内のその次の3つのトランザクションは、11個のレコードで構成されているCT、2つのレコードで構成されているDTおよび2つのレコードで構成されているETになります。CTのコミット・レコードの前にDTトランザクションのコミット・レコードが書き込まれるため、次にttXlaNextUpdateをコールすると、DTトランザクションの2つのレコードが返され、nreturnedの値は2になります。その次にttXlaNextUpdateをコールすると、XLAによって、CTトランザクションの合計レコード数がmaxrecordsを超えていることが検出され、このトランザクションのレコードが2つのバッチで返されます。最初のバッチには、CTの最初の10個のレコード(nreturned = 10)が含まれます。2番目のバッチには、CTの最後のレコードおよびETトランザクションの2つのレコード(ETの後のトランザクションのコミット・レコードが次の7つのレコード内で検出されないと想定)が含まれます。
これらの関数のパラメータの詳細は、「ttXlaNextUpdate」および「ttXlaNextUpdateWait」を参照してください。
XLAは、レコードをメモリー・バッファまたはディスク上のトランザクション・ログ・ファイルから読み取ります(「XLAでレコードをトランザクション・ログから読み取る方法」を参照)。待機時間を最小にするために、メモリー・バッファからのレコードは使用可能になるとすぐに返されますが、バッファにないレコードはバッファが空の場合にのみ返されます。この設計によって、XLAアプリケーションでは、行われた変更を最小の待機時間ですぐに表示できます。トレードオフとして、ttXlaNextUpdateまたはttXlaNextUpdateWaitのmaxrecordsパラメータでリクエストした数より少ない数の変更が返される場合があります。
|
注意: スループットを最適化するために、XLAアプリケーションでは、フェッチおよびレコード処理の手順を非同期にする必要があります。たとえば、1つのスレッドを作成してレコードのフェッチおよび保存を行ったり、1つ以上のスレッドを作成して保存したレコードを処理することができます。 |
更新レコードの配列が存在し、その各レコードが示す処理のタイプがわかるため、返された行データを確認できます。
ttXlaNextUpdateまたはttXlaNextUpdateWait関数によって返される各レコードは、ttXlaUpdateDesc_tヘッダーで始まり、このヘッダーには次の情報が記述されています。
処理が実行された表
レコードがトランザクションの最初のレコードであるか、または最後の(コミット)レコードであるか
レコードが示す処理のタイプ
返された行データの長さ(行データがある場合)
行内の更新された列(該当する列がある場合)
図5-7に、トランザクション・ログ内の更新レコードの例を示します。
ttXlaUpdateDesc_tヘッダーは固定長で、処理のタイプに応じて、データベースから0(ゼロ)から2行の行(タプル)が続きます。ttXlaUpdateDesc_tヘッダーのアドレスを取得し、それにsizeof(ttXlaUpdateDesc_t)を追加することによって、最初に返された行のアドレスを特定できます。
tup1 = (void*) ((char*) ttXlaUpdateDesc_t + sizeof(ttXlaUpdateDesc_t));
これを次の例5-4に示します。
ttXlaUpdateDesc_t ->typeフィールドは、更新を生成したSQL処理のタイプについての記述です。UPDATETTUP型のトランザクション・レコードは、UPDATE処理についての記述であるため、更新の前後の行データをレポートするために2行の行を返します。更新後の値が含まれている2番目に返された行のアドレスは、レコード内の1つ目の行のアドレスにその長さに追加することで特定できます。
if (ttXlaUpdateDesc_t->type == UPDATETUP) { tup2 = (void*) ((char*) tup1 + ttXlaUpdateDesc_t->tuple1); }
これも例5-4に示します。
例5-4 レコード・ヘッダーでのSQL処理タイプの確認
この例では、ttXlaNextUpdateWait関数によって返された各レコードをHandleChange()関数に渡し、この関数では、そのレコードがINSERT、UPDATEまたはCREATE VIEW処理に関連しているかを判別します。この例では、わかりやすくするために、その他の処理はすべて無視されています。
HandleChange()関数は、PrintColValues()関数(例5-13を参照)をコールする前に各タイプのSQL処理を異なる方法で処理します。
void HandleChange(ttXlaUpdateDesc_t* xlaP)
{
void* tup1;
void* tup2;
/* First confirm that the XLA update is for the table we care about. */
if (xlaP->sysTableID != SYSTEM_TABLE_ID)
return ;
/* OK, it's for the table we're monitoring. */
/* The last record in the ttXlaUpdateDesc_t record is the "tuple2"
* field. Immediately following this field is the first XLA record "row". */
tup1 = (void*) ((char*) xlaP + sizeof(ttXlaUpdateDesc_t));
switch(xlaP->type) {
case INSERTTUP:
printf("Inserted new row:\n");
PrintColValues(tup1);
break;
case UPDATETUP:
/* If this is an update ttXlaUpdateDesc_t, then following that is
* the second XLA record "row".
*/
tup2 = (void*) ((char*) tup1 + xlaP->tuple1);
printf("Updated row:\n");
PrintColValues(tup1);
printf("To:\n");
PrintColValues(tup2);
break;
case DELETETUP:
printf("Deleted row:\n");
PrintColValues(tup1);
break;
default:
/* Ignore any XLA records that are not for inserts/update/delete SQL ops. */
break;
} /* switch (xlaP->type) */
}
更新レコードでは、0(ゼロ)から2行のデータがttXlaUpdateDesc_t構造体の後に返される場合があります(「レコード・ヘッダーの確認および行アドレスの検出」を参照)。図5-8に示すように、各行のデータの最初の部分は固定長で、その後に可変長データが続きます。
列データを確認する手順は次のとおりです。
返された行から列値を読み取るには、まず、その行の各列のオフセットを判別する必要があります。列オフセットおよびその他の列メタデータは、特定の表に対してttXlaGetColumnInfo関数をコールすると取得でき、この関数は表の列ごとに個別のttXlaColDesc_t構造体を返します。ttXlaGetColumnInfo関数は、初期化プロシージャの一部としてコールする必要があります。「XLAの初期化およびXLAハンドルの取得」の説明では、わかりやすくするためにこのコールは省略されています。
ttXlaGetColumnInfoをコールする場合は、colinfoパラメータを指定して、返されるttXlaColDesc_t構造体のリストを保持するバッファへのポインタを作成します。maxcolsパラメータを使用して、バッファのサイズを定義します。
例5-5 列記述の使用
次に示すxlaSimpleデモのコード例では、返される列の最大数(MAX_XLA_COLUMNS)を推測し、返されるttXlaColDesc_t構造体を保持するバッファ(xla_column_defs)のサイズを設定します。maxcolsパラメータを設定する場合のもう1つのより正確な方法として、ttXlaGetTableInfo関数をコールし、ttXlaColDesc_t ->columnsに返された値を使用する方法があります。
#define MAX_XLA_COLUMNS 128
...
SQLINTEGER ncols;
...
ttXlaColDesc_t xla_column_defs[MAX_XLA_COLUMNS];
...
rc = ttXlaGetColumnInfo(xla_handle, SYSTEM_TABLE_ID, userID,
xla_column_defs, MAX_XLA_COLUMNS, &ncols);
if (rc != SQL_SUCCESS {
/* See "Handling XLA errors" */
}
図5-9に示すように、ttXlaGetColumnInfo関数は次の情報を出力します。
ttXlaGetColumnInfo colinfoパラメータが指すバッファに格納されるttXlaColDesc_t構造体のリスト(xla_column_defs)
xla_column_defsバッファに実際に返される列数を保持するnreturnedの値(ncols)
図5-9 ttXlaGetColumnInfoによって返されるttXlaColDesc_t構造体

ttXlaGetColumnInfoによって返される各ttXlaColDesc_t構造体には、その列のオフセット位置を記述するoffset値が含まれます。列データを読み込む際にこのoffset値をどのように使用するかは、列が固定長データ(CHAR、NCHAR、INTEGER、BINARY、DOUBLE、FLOAT、DATE、TIME、TIMESTAMPなど)を含んでいるか、可変長データ(VARCHAR、NVARCHAR、VARBINARYなど)を含んでいるかによって異なります。
固定長列データの場合、列のアドレスは、ttXlaColDesc_t構造体のoffset値に行のアドレスを追加したものです。
例5-6 固定長列データの読取り
ここに示すような計算の完全な動作例については、例5-13を参照してください。
MYDATA表の最初の列はCHAR型です。HandleChange()関数(例5-4)で前に取得したtup1行のアドレスと、ttXlaGetColumnInfo関数(例5-5)によって返された最初のttXlaColDesc_t構造体のoffsetを使用する場合は、次のように計算して最初の列の値を取得できます。
char* Column1; Column1 = ((unsigned char*) tup1 + xla_column_defs[0].offset);
MYDATA表の3番目の列はINTEGER型のため、3番目のttXlaColDesc_t構造体のoffsetを使用して値を検出し、次のように計算して、整数として再キャストできます。データは適切に配置されます。
int Column3;
Column3 = *((int*) ((unsigned char*) tup +
xla_column_defs[2].offset));
MYDATA表の4番目の列はNCHAR型のため、4番目のttXlaColDesc_t構造体のoffsetを使用して値を検出し、次のように計算して、SQLWCHAR型として再キャストできます。
SQLWCHAR* Column4;
Column4 = (SQLWCHAR*) ((unsigned char*) tup +
xla_column_defs[3].offset);
前述の例で取得した列値とは異なり、Column4は2バイトのUnicode文字の配列を指します。文字列を取得するには、例5-13のSQL_WCHARの場合のように、この配列内の各要素に対して繰り返し実行する必要があります。
その他の固定長データ型は、対応するCの型にキャストできます。複合固定長データ型(DATE、TIME、DECIMAL値など)は、TimesTenの内部形式で格納されますが、アプリケーションでXLA変換関数を使用して、対応するODBC C値に変換できます(「複合データ型の変換」を参照)。
NOT INLINE可変長データ(VARCHAR、NVARCHARおよびVARBINARY)の場合、ttXlaColDesc_t ->offsetにあるデータは、返された行の可変長部分でのデータの場所を指す4バイトのオフセット値です。オフセット・アドレスをオフセット値に追加すると、行の可変長部分の列データのアドレスを取得できます。この場所の最初のnバイトはデータの長さで、その後に実際のデータが続きます(nは、32ビットのプラットフォームでは4、64ビットのプラットフォームでは8です)。可変長データの場合、ttXlaColDesc_t ->size値は列の許容最大サイズです。図5-11に、行内のNOT INLINE可変長データを検出する方法を示します。
例5-7 NOT INLINE可変長列データの読取り
ここに示すような計算の完全な動作例については、例5-13「完全なPrintColValues()関数」を参照してください。
引き続きこの例では、返された行(tup1)の2番目の列はVARCHAR型です。行内の可変長データを検出するには、まず前述の図5-11に示すように、行の固定長部分で列のttXlaColDesc_t ->offsetの値を検出します。このアドレスの値は、行の可変長部分のデータの4バイトのオフセットです(VarOffset)。次に、VarOffsetのアドレスにVarOffsetオフセット値を追加して、可変長列データの先頭へのポインタを取得します(DataLength)。32ビット・プラットフォームで処理が行われると想定すると、DataLength位置の最初の4バイトがデータの長さです。DataLengthの後の次のバイトが、実際のデータの先頭です(Column2)。
このコード例では、32ビット・プラットフォームで処理が行われると想定しているため、DataLengthは32ビット・タイプとして初期化されます。64ビット・プラットフォームでは、DataLengthを64ビット・タイプとして初期化する必要があり、Column2データはオフセット・アドレスDataLengthの後に64ビット+1で書き込まれます。
void* VarOffset; /* offset of data */
long* DataLength; /* length of data */
char* Column2; /* pointer to data */
VarOffset = (void*) ((unsigned char*) tup1 +
xla_column_defs[1].offset);
/*
* If column is out-of-line, pColVal points to an offset
* else column is inline so pColVal points directly to the string length.
*/
if (xla_column_defs[1].flags & TT_COLOUTOFLINE)
DataLength = (long*)((char*)VarOffset + *((int*)VarOffset));
else
DataLength = (long*)VarOffset;
Column2 = (char*)(DataLength+1);
VARBINARY型は、VARCHAR型での方法とほぼ同じ方法で処理されます。Column2がNVARCHAR型の場合、SQLWCHARとして初期化し、前述のVARCHARの場合と同様に値を取得し、次に例5-13のNCHAR値CharBufの場合と同様にColumn2配列に対して繰り返し実行できます。
|
注意: 前述の例では、DataLengthはデータ型longで、64-bitのシステムでは64ビット(8バイト)型で、32-bitのシステムでは32ビット(4バイト)型として記載されています。ほとんどのUNIXシステムに当てはまりますが、Windowsの64-bitシステムのlongは4バイト型です。 |
レコードの行データから返される文字列は空文字では終了していません。文字列をバッファにコピーし、その文字列の最後の文字の後に\0などの空文字を追加すると、文字列を空文字で終了できます。
文字列を空文字で終了する手順は、文字列が固定長の場合と可変長の場合で少し異なります。例5-8に、固定長文字列を空文字で終了する手順を示します。その後の例5-9には、サイズがわかっている可変長文字列を空文字で終了する手順を示します。例5-10には、サイズがわかっていない文字列の場合の手順を示します。
例5-8 固定長文字列の空文字での終了
ここに示すような計算の完全な動作例については、例5-13を参照してください。
例5-6で返された固定長CHAR(10) Column1文字列を空文字で終了するには、文字列と空文字を保持できる十分な大きさのバッファを設定します。次に、ttXlaColDesc_t ->sizeから文字列のサイズを取得し、文字列をバッファにコピーして、次のような計算で文字列の末尾を空文字で終了します。これで、バッファの内容を使用できます。この例では、その文字列を出力します。
char buffer[10+1];
int size;
size = xla_column_defs[0].size;
memcpy(buffer, Column1, size);
buffer[size] = '\0';
printf(" Row %s is %s\n", ((unsigned char*) xla_column_defs[0].colName), buffer);
可変長文字列を空文字で終了する手順は、固定長文字列の手順とほぼ同じです。可変長データのオフセットの先頭にある値は、文字列のサイズのみです(「NOT INLINE可変長列データの読取り」を参照)。
例5-9 サイズがわかっている可変長文字列の空文字での終了
(ここに示すような計算の完全な動作例については、例5-13を参照してください。)
例5-7で取得したColumn2文字列がVARCHAR(32)の場合、文字列と空文字を保持できる十分な大きさのバッファを設定します。DataLengthオフセットにある値を使用して、次のような計算で文字列のサイズを設定します。
char buffer[32+1];
memcpy(buffer, Column2, *DataLength);
buffer[*DataLength] = '\0';
printf(" Row %s is %s\n", ((unsigned char*) xla_column_defs[1].colName), buffer);
すべてのデータ型を読み取るための汎用コードを記述する場合は、返される文字列のサイズを想定できません。サイズがわからない文字列に対しては、返された文字列の大部分を保持できる十分な大きさのバッファを静的に割り当てます。返された文字列がバッファより大きい場合は、例5-10に示すように、正しいサイズのバッファを動的に割り当てます。
例5-10 サイズがわかっていない可変長文字列の空文字での終了
例5-7で取得したColumn2文字列のサイズがわからない場合は、最大10000文字の文字列を保持できる十分な大きさのバッファを静的に割り当てます。その後、可変長データ・オフセットの先頭で取得したDataLength値が、バッファのサイズより小さいことを確認します。文字列がバッファより大きい場合は、malloc()を使用してバッファを動的に正しいサイズに割り当てます。
#define STACKBUFSIZE 10000
char VarStackBuf[STACKBUFSIZE];
char* buffer;
buffer = (*DataLength+1 <= STACKBUFSIZE) ? VarStackBuf :
malloc(*DataLength+1);
memcpy(buffer,Column2,*DataLength);
buffer[*DataLength] = '\0';
printf(" Row %s is %s\n", ((unsigned char*) xla_column_defs[1].colName), buffer);
if (buffer != VarStackBuf) /* buffer was allocated */
free(buffer);
TT_DATE、TT_TIME、TT_DECIMALなどの複合データ型の値は、TimesTenの内部形式で保存され、これらはXLA型変換関数を使用して、対応するODBC C型に変換できます。表5-2に、これらの変換関数の説明を示します。
表5-2 XLAデータ型変換関数
| 関数 | 変換 |
|---|---|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
|
|
内部 |
これらの変換関数は、ttXlaUpdateDesc_t型(UPDATETUP、INSERTTUPおよびDELETETUP)に含まれる行データで使用できます。
例5-11 複合データ型の変換
(ここに示すような計算の完全な動作例については、例5-13を参照してください。)
前にHandleChange()関数(例5-4)で取得したtup1行のアドレス、およびttXlaGetColumnInfo関数(例5-5)によって返された5番目のttXlaColDesc_t構造体のoffsetを使用すると、TIMESTAMP型の列値を検出できます。ttXlaTimeStampToODBCCType関数を使用し、TimesTen形式から列データを変換し、変換されたTIME値をODBC TIMESTAMP_STRUCTに保存します。次のようなコードを使用すると値を出力できます。
void* Column5;
TIMESTAMP_STRUCT timestamp;
Column5 = (void*) ((unsigned char*) tup1 +
xla_column_defs[4].offset);
rc = ttXlaTimeStampToODBCCType(Column5, ×tamp);
if (rc != SQL_SUCCESS) {
/* See "Handling XLA errors" */
}
printf(" %s: %04d-%02d-%02d %02d:%02d:%02d.%06d\n",
((unsigned char*) xla_column_defs[i].colName),
timestamp.year,timestamp.month, timestamp.day,
timestamp.hour,timestamp.minute,timestamp.second,
timestamp.fraction);
前にHandleChange()関数(例5-4)で取得したtup1行のアドレス、およびttXlaGetColumnInfo関数(例5-5)によって返された6番目のttXlaColDesc_t構造体のoffsetを使用すると、DECIMAL型の列値を検出できます。ttXlaDecimalToCString関数を使用して、TimesTenのDECIMAL形式から文字列に列データを変換します。次のようなコードを使用すると値を出力できます。
char decimalData[50];
Column6 = (float*) ((unsigned char*) tup +
xla_column_defs[5].offset);
precision = (short) (xla_column_defs[5].precision);
scale = (short) (xla_column_defs[5].scale);
rc = ttXlaDecimalToCString(Column6, (char*)&decimalData,
precision, scale);
if (rc != SQL_SUCCESS) {
/* See "Handling XLA errors" */
}
printf(" %s: %s\n", ((unsigned char*) xla_column_defs[5].colName), decimalData);
NULL値可能な表の列の場合、ttXlaColDesc_t ->nullOffsetは、レコードの列のNULLバイトを指します。列にNULLを指定できない場合、このフィールドは0(ゼロ)で、列にNULLを指定できる場合、このフィールドは0より大きくなります。
NULL値可能な列の場合(ttXlaColDesc_t ->nullOffset > 0)、列がNULLであるかどうかを確認するには、NULLオフセットをttXlaUpdate_t*のアドレスに追加し、(unsigned char)バイトをチェックして1 (NULL)または0 (NOT NULL)のいずれであるかを確認します。
例5-13に、各列のttXlaColDesc_t ->dataTypeを確認して、データ型がCHAR、NCHAR、INTEGER、TIMESTAMP、DECIMALおよびVARCHARである列を検出し、その値を出力する関数を示します。これは、考えられる1つの方法にすぎません。たとえば、別の方法として、ttXlaColDesc_t ->ColNameの値を確認して、名前で特定の列を検出する方法もあります。
PrintColValues()関数は、長さが最大50バイトのCHARおよびVARCHARの文字列を処理します。NCHAR文字は、ASCIIキャラクタ・セットに属している必要があります。
例5-13 完全なPrintColValues()関数
この例では、関数はまずttXlaColDesc_t ->nullOffsetを調べて列がNULLかどうかを確認します。次に、ttXlaColDesc_t ->dataTypeのフィールドを調べて列のデータ型を確認します。単純な固定長データ(CHAR、NCHARおよびINTEGER)の場合は、ttXlaColDesc_t ->offsetにある値を適切なCの型にキャストします。複合データ型(TIMESTAMPおよびDECIMAL)は、ttXlaTimeStampToODBCCType関数およびttXlaDecimalToCString関数を使用して、TimesTen形式からODBC C値に変換されます。
可変長データ(VARCHAR)の場合、関数は行の可変長部分でデータを検出します(「XLAエラーの処理」を参照)。
void PrintColValues(void* tup)
{
SQLRETURN rc ;
SQLINTEGER native_error;
void* pColVal;
char buffer[50+1]; /* No strings over 50 bytes */
int i;
for (i = 0; i < ncols; i++)
{
if (xla_column_defs[i].nullOffset != 0) { /* See if column is NULL */
/* this means col could be NULL */
if (*((unsigned char*) tup + xla_column_defs[i].nullOffset) == 1) {
/* this means that value is SQL NULL */
printf(" %s: NULL\n",
((unsigned char*) xla_column_defs[i].colName));
continue; /* Skip rest and re-loop */
}
}
/* Fixed-length data types: */
/* For INTEGER, recast as int */
if (xla_column_defs[i].dataType == TTXLA_INTEGER) {
printf(" %s: %d\n",
((unsigned char*) xla_column_defs[i].colName),
*((int*) ((unsigned char*) tup + xla_column_defs[i].offset)));
}
/* For CHAR, just get value and null-terminate string */
else if ( xla_column_defs[i].dataType == TTXLA_CHAR_TT
|| xla_column_defs[i].dataType == TTXLA_CHAR) {
pColVal = (void*) ((unsigned char*) tup + xla_column_defs[i].offset);
memcpy(buffer, pColVal, xla_column_defs[i].size);
buffer[xla_column_defs[i].size] = '\0';
printf(" %s: %s\n", ((unsigned char*) xla_column_defs[i].colName), buffer);
}
/* For NCHAR, recast as SQLWCHAR.
NCHAR strings must be parsed one character at a time */
else if ( xla_column_defs[i].dataType == TTXLA_NCHAR_TT
|| xla_column_defs[i].dataType == TTXLA_NCHAR ) {
SQLUINTEGER j;
SQLWCHAR* CharBuf;
CharBuf = (SQLWCHAR*) ((unsigned char*) tup + xla_column_defs[i].offset);
printf(" %s: ", ((unsigned char*) xla_column_defs[i].colName));
for (j = 0; j < xla_column_defs[i].size / 2; j++)
{
printf("%c", CharBuf[j]);
}
printf("\n");
}
/* Variable-length data types:
For VARCHAR, locate value at its variable-length offset and null-terminate.
VARBINARY types are handled in a similar manner.
For NVARCHARs, initialize 'var_data' as a SQLWCHAR, get the value as shown
below, then iterate through 'var_len' as shown for NCHAR above */
else if ( xla_column_defs[i].dataType == TTXLA_VARCHAR
|| xla_column_defs[i].dataType == TTXLA_VARCHAR_TT) {
long* var_len;
char* var_data;
pColVal = (void*) ((unsigned char*) tup + xla_column_defs[i].offset);
/*
* If column is out-of-line, pColVal points to an offset
* else column is inline so pColVal points directly to the string length.
*/
if (xla_column_defs[i].flags & TT_COLOUTOFLINE)
var_len = (long*)((char*)pColVal + *((int*)pColVal));
else
var_len = (long*)pColVal;
var_data = (char*)(var_len+1);
memcpy(buffer,var_data,*var_len);
buffer[*var_len] = '\0';
printf(" %s: %s\n", ((unsigned char*) xla_column_defs[i].colName), buffer);
}
/* Complex data types require conversion by the XLA conversion methods
Read and convert a TimesTen TIMESTAMP value.
DATE and TIME types are handled in a similar manner */
else if ( xla_column_defs[i].dataType == TTXLA_TIMESTAMP
|| xla_column_defs[i].dataType == TTXLA_TIMESTAMP_TT) {
TIMESTAMP_STRUCT timestamp;
char* convFunc;
pColVal = (void*) ((unsigned char*) tup + xla_column_defs[i].offset);
if (xla_column_defs[i].dataType == TTXLA_TIMESTAMP_TT) {
rc = ttXlaTimeStampToODBCCType(pColVal, ×tamp);
convFunc="ttXlaTimeStampToODBCCType";
}
else {
rc = ttXlaOraTimeStampToODBCTimeStamp(pColVal, ×tamp);
convFunc="ttXlaOraTimeStampToODBCTimeStamp";
}
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
fprintf(stderr, "%s() returns an error <%d>: %s",
convFunc, rc, err_buf);
TerminateGracefully(1);
}
printf(" %s: %04d-%02d-%02d %02d:%02d:%02d.%06d\n",
((unsigned char*) xla_column_defs[i].colName),
timestamp.year,timestamp.month, timestamp.day,
timestamp.hour,timestamp.minute,timestamp.second,
timestamp.fraction);
}
/* Read and convert a TimesTen DECIMAL value to a string. */
else if (xla_column_defs[i].dataType == TTXLA_DECIMAL_TT) {
char decimalData[50];
short precision, scale;
pColVal = (float*) ((unsigned char*) tup + xla_column_defs[i].offset);
precision = (short) (xla_column_defs[i].precision);
scale = (short) (xla_column_defs[i].scale);
rc = ttXlaDecimalToCString(pColVal, (char*)&decimalData, precision, scale);
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
fprintf(stderr, "ttXlaDecimalToCString() returns an error <%d>: %s",
rc, err_buf);
TerminateGracefully(1);
}
printf(" %s: %s\n", ((unsigned char*) xla_column_defs[i].colName),
decimalData);
}
else if (xla_column_defs[i].dataType == TTXLA_NUMBER) {
char numbuf[32];
pColVal = (void*) ((unsigned char*) tup + xla_column_defs[i].offset);
rc=ttXlaNumberToCString(xla_handle, pColVal, numbuf, sizeof(numbuf));
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
fprintf(stderr, "ttXlaNumberToDouble() returns an error <%d>: %s",
rc, err_buf);
TerminateGracefully(1);
}
printf(" %s: %s\n", ((unsigned char*) xla_column_defs[i].colName), numbuf);
}
} /* End FOR loop */
}
|
注意:
|
ODBCまたはXLA関数をコールするたびに、エラーのリターン・コードを確認する必要があります。エラーが致命的である場合は、「XLAアプリケーションの終了」の説明に従ってプログラムを終了します。
エラーは、エラー・コード(エラー番号)またはtt_Err文字列のいずれかを使用して確認できます。TimesTenエラー・コードとエラー文字列の完全なリストは、install_dir/include/tt_errCode.hファイルを参照してください。各メッセージについては、『Oracle TimesTen In-Memory Databaseエラー・メッセージおよびSNMPトラップ』のエラーおよび警告のリストに関する項を参照してください。
XLA関数のリターン・コードがSQL_SUCCESSでない場合は、ttXlaError関数を使用して、XLAハンドルでXLA固有のエラーを取得します。
「エラーのチェック」も参照してください。
例5-14 リターン・コードの確認とエラー処理関数のコール
この例では、XLA関数ttXlaTableByNameをコールした後、リターン・コードがSQL_SUCCESSであるかどうかを確認します。そうでない場合は、XLAエラー処理関数をコールし、その後アプリケーションを終了する関数をコールします。「XLAアプリケーションの終了」も参照してください。
rc = ttXlaTableByName(xla_handle, TABLE_OWNER, TABLE_NAME,
&SYSTEM_TABLE_ID, &userID);
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
fprintf(stderr,
"ttXlaTableByName() returns an error <%d>: %s", rc, err_buf);
TerminateGracefully(1);
}
XLAエラー処理関数は、エラー・スタックからすべてのXLAエラーが読み取られまで繰り返しttXlaErrorをコールする必要があり、ttXlaErrorからのリターン・コードがSQL_NO_DATA_FOUNDになるまで続行します。エラーを再度読み取る必要がある場合は、ttXlaErrorRestart関数をコールしてエラー・スタックのポインタを最初のエラーに再設定します。
エラー・スタックは、ttXlaErrorまたはttXlaErrorRestart以外のすべてのXLA関数へのコール後に消去されます。
|
注意: ttXlaPersistOpenがXLAハンドルを作成できない場合は、エラー・コードSQL_INVALID_HANDLEを返します。XLAハンドルが作成されていないため、ttXlaErrorを使用してこのエラーを検出することはできません。SQL_INVALID_HANDLEは、メモリーが割り当てることができない場合または指定したパラメータが無効である場合にのみ返されます。 |
アプリケーションによっては、表5-3に示すような特定のXLAエラーに対処する必要がある場合があります。
表5-3 XLAエラーとコード
| エラー | コード |
|---|---|
|
802(一時的) |
|
|
6001(一時的) |
|
|
6002(一時的) |
|
|
6003(一時的) |
|
|
6220(一時的) |
|
|
6221(一時的) |
|
|
8024 |
|
|
8029 |
|
|
8031 |
|
|
8034 |
|
|
8035 |
|
|
8036 |
|
|
8037 |
|
|
8038 |
|
|
8046 |
|
|
8047 |
例5-15 handleXLAerror()関数のコール
この例では、xlaSimpleデモ・プログラムのエラー関数であるhandleXLAerror()を示します。
void handleXLAerror(SQLRETURN rc, ttXlaHandle_h xlaHandle,
SQLCHAR* err_msg, SQLINTEGER* native_error)
{
SQLINTEGER retLen;
SQLINTEGER code;
char* err_msg_ptr;
/* initialize return codes */
rc = SQL_ERROR;
*native_error = -1;
err_msg[0] = '\0';
err_msg_ptr = (char*)err_msg;
while (1)
{
int rc = ttXlaError(xlaHandle, &code, err_msg_ptr,
ERR_BUF_LEN - (err_msg_ptr - (char*)err_msg), &retLen);
if (rc == SQL_NO_DATA_FOUND)
{
break;
}
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
sprintf(err_msg_ptr,
"*** Error fetching error message via ttXlaError(); rc=<%d>.",rc) ;
break;
}
rc = SQL_ERROR;
*native_error = code ;
/* append any other error messages */
err_msg_ptr += retLen;
}
}
XLAブックマークがサブスクライブされている表を削除するには、ブックマークから表をサブスクライブ解除する必要があります。ブックマークから表をサブスクライブ解除するには、アプリケーションがブックマークに接続されているかどうかに応じて、いくつかの方法があります。
XLAアプリケーションが接続されており、削除する表を追跡しているブックマークを使用している場合は、次のタスクを実行します。
各XLAアプリケーションでttXlaTableStatus関数をコールし、newstatusパラメータを0に設定します。これにより、アプリケーションで使用中のXLAブックマークから表がサブスクライブ解除されます。
表を削除します。
XLAアプリケーションが接続されておらず、削除する表に関連付けられているブックマークを使用している場合は、次のタスクを実行します。
SYS.XLASUBSCRIPTIONSシステム表を問い合せて、削除する表にサブスクライブされているブックマークを確認します。
ttXlaUnsubscribe組込みプロシージャを使用して、表にサブスクライブされている各XLAブックマークから表をサブスクライブ解除します。
表を削除します。
ブックマークを削除すると、XLAブックマークからも表がサブスクライブ解除されます。次の項「ブックマークの削除」を参照してください。
アプリケーションを終了する場合や表を削除する場合に、ブックマークを削除することがあります。アプリケーションが接続されており、ブックマークを使用している場合は、ttXlaDeleteBookmark関数を使用してXLAブックマークを削除します。
ブックマークは、前の接続がクローズされた後に新しい接続で再利用される可能性があります(「XLAブックマークについて」を参照)。新しい接続は、前の接続が停止した場所からトランザクション・ログの読取りを再開します。次の点に注意してください。
ブックマークを削除すると、後続のチェックポイント処理(組込みプロシージャttCkpt、ttCkptBlockingなど)によって、トランザクション・ログ内の未読の更新レコードに関連するディスク領域が解放されます。
ブックマークを削除しない場合、XLAアプリケーションは、接続してブックマークを再利用する際に、プログラムの終了以降累積していたすべての未読の更新レコードを読み取ります。これは、更新レコードがTimesTenトランザクション・ログで永続的であるためです。ただし、これらの未読のレコードがトランザクション・ログ・ファイルに累積し、大量のディスク領域を使用すると危険です。
|
注意:
|
例5-16 ブックマークの削除
次の例に示すように、xlaSimpleデモのInitHandler()関数は、終了時にXLAブックマークを削除します。
if (deleteBookmark) {
ttXlaDeleteBookmark(xla_handle);
if (rc != SQL_SUCCESS) {
/* See "Handling XLA errors" */
}
xla_handle = NULL; /* Deleting the bookmark has the */
/* effect of disconnecting from XLA. */
}
/* Close the XLA connection as described in the next section,
"Terminating an XLA application". */
アプリケーションが接続されておらず、XLAブックマークを使用している場合、次のいずれかの方法でブックマークを削除できます。
XLAアプリケーションでトランザクション・ログの読取りを終了したら、コミットされていないトランザクションをロールバックし、すべてのハンドルを解放して、正常に終了する必要があります。これには次の2つの方法があります。
すべての表およびマテリアライズド・ビューのサブスクライブを解除し、XLAブックマークを削除し、データベースから切断します。
または
データベースからは切断しますが、XLAブックマークは維持します。後で再接続する際に、ブックマークからレコードの読取りを再開できます。
最初の方法では、次の手順を実行します。
ttXlaTableStatusをコールして各表およびマテリアライズド・ビューをサブスクライブ解除します(newstatusパラメータを0(ゼロ)に設定します)。
ttXlaDeleteBookmarkをコールして、ブックマークを削除します。「ブックマークの削除」を参照してください。
ttXlaCloseをコールして、XLAハンドルを切断します。
SQL_ROLLBACKを指定してODBC関数SQLTransactをコールし、コミットされていないトランザクションをすべてロールバックします。
ODBC関数SQLDisconnectをコールして、TimesTenデータベースから切断します。
ODBC関数SQLFreeConnectをコールして、ODBC接続ハンドルに割り当てられているメモリーを解放します。
ODBC関数SQLFreeEnvをコールして、ODBC環境ハンドルを解放します。
2番目の方法では、ブックマークを維持するため、最初の2つの手順を省略し残りの手順を完了します。
割当てとは反対の順序でリソースが解放されることに注意してください。たとえば、ODBC環境ハンドルはODBC接続ハンドルより先に割り当てられているため、クリーンアップでは、接続ハンドルを環境ハンドルより先に解放します。
例5-17 XLAアプリケーションの終了
この例では、xlaSimpleクイック・スタート・デモの終了関数であるTerminateGracefully()を示します。
void TerminateGracefully(int status)
{
SQLRETURN rc;
SQLINTEGER native_error ;
SQLINTEGER oldstatus;
SQLINTEGER newstatus = 0;
/* If the table has been subscribed to through XLA, unsubscribe it. */
if (SYSTEM_TABLE_ID != 0) {
rc = ttXlaTableStatus(xla_handle, SYSTEM_TABLE_ID, 0,
&oldstatus, &newstatus);
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
fprintf(stderr, "Error when unsubscribing from "TABLE_OWNER"."TABLE_NAME
" table <%d>: %s", rc, err_buf);
}
SYSTEM_TABLE_ID = 0;
}
/* Close the XLA connection. */
if (xla_handle != NULL) {
rc = ttXlaClose(xla_handle);
if (rc != SQL_SUCCESS) {
fprintf(stderr, "Error when disconnecting from XLA:<%d>", rc);
}
xla_handle = NULL;
}
if (hstmt != SQL_NULL_HSTMT) {
rc = SQLFreeStmt(hstmt, SQL_DROP);
if (rc != SQL_SUCCESS) {
handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
fprintf(stderr, "Error when freeing statement handle:\n%s\n", err_buf);
}
hstmt = SQL_NULL_HSTMT;
}
/* Disconnect from TimesTen entirely. */
if (hdbc != SQL_NULL_HDBC) {
rc = SQLTransact(henv, hdbc, SQL_ROLLBACK);
if (rc != SQL_SUCCESS) {
handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
fprintf(stderr, "Error when rolling back transaction:\n%s\n", err_buf);
}
rc = SQLDisconnect(hdbc);
if (rc != SQL_SUCCESS) {
handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
fprintf(stderr, "Error when disconnecting from TimesTen:\n%s\n", err_buf);
}
rc = SQLFreeConnect(hdbc);
if (rc != SQL_SUCCESS) {
handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
fprintf(stderr, "Error when freeing connection handle:\n%s\n", err_buf);
}
hdbc = SQL_NULL_HDBC;
}
if (henv != SQL_NULL_HENV) {
rc = SQLFreeEnv(henv);
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
handleError(rc, henv, hdbc, hstmt, err_buf, &native_error);
fprintf(stderr, "Error when freeing environment handle:\n%s\n", err_buf);
}
henv = SQL_NULL_HENV;
}
exit(status);
}
『Oracle TimesTen In-Memory Database開発者および管理者ガイド』に記載されているとおり、TimesTenのレプリケーションは、ほとんどの顧客要件を満たすには十分ですが、XLA関数を使用して、1つのデータベースから別のデータベースに更新をレプリケートすることもできます。この方法で独自のレプリケーション・スキームをXLAの上に実装することはかなり困難ですが、TimesTenのレプリケーションがなんらかの理由で実行できない場合には検討できます。
|
注意: 異なるプラットフォーム間または同じプラットフォームの32ビットと64ビットのバージョン間では、XLAを使用して更新をレプリケートできません。 |
この項では、送信側のデータベースをマスター、受信側のデータベースをサブスクライバと呼びます。XLAを使用してデータベース間で変更をレプリケートするには、まずttXlaPersistOpen関数を使用してXLAハンドルを初期化します(「XLAの初期化およびXLAハンドルの取得」を参照)。
XLAハンドルをデータベースに対して初期化した後、次の項の手順を実行します。
ここで説明するXLA関数については、第9章「XLAリファレンス」を参照してください。
データベース間で更新レコードを送信する場合は、マスター・データベースとサブスクライバ・データベースの表に互換性があることを確認します。
ttXlaTableByName、ttXlaGetTableInfoおよびttXlaGetColumnInfo関数を使用して、表とその列の記述を確認できます。次の項「表および列の記述の確認」を参照してください。
ttXlaVersionTableInfoおよびttXlaVersionColumnInfo関数を使用して、特定のXLAレコードの表および列のバージョンを確認できます。次の「表および列のバージョンの確認」を参照してください。
ttXlaTableByName、ttXlaGetTableInfoおよびttXlaGetColumnInfo関数を使用すると、レプリケートする各表のttXlaTblDesc_tおよびttXlaColDesc_t記述が返されます。これらの処理については、「更新を監視する表の指定」および「列の記述の取得」を参照してください。次に、これらの記述をttXlaTableCheck関数に渡すことができます。出力パラメータcompatは、表に互換性があるかどうかを指定します。値が1の場合は互換性があり、値が0(ゼロ)の場合は互換性がないことを示します。次の例でこれを説明します。
ttXlaVersionTableInfoおよびttXlaVersionColumnInfo関数を使用して、レコードの生成時に更新レコードの表構造の情報を取得します。
次の例では、pCmdソースのpXlaRecord更新レコードに関係付けられている表がhXlaTargetターゲットと互換性があることを確認します。
例5-19 表および列のバージョンの互換性の確認
BOOL CUTLCheckXlaTable (SCOMMAND* pCmd,
ttXlaHandle_h hXlaTarget,
const ttXlaUpdateDesc_t* pXlaRecord)
{
/* locals */
ttXlaTblVerDesc_t tblVerDescSource;
ttXlaColDesc_t colDescSource [255];
SQLINTEGER iColsReturned = 0;
SQLINTEGER iCompatible = 0;
SQLRETURN rc;
/* only certain update record types should be checked */
if (pXlaRecord->type == INSERTTUP ||
pXlaRecord->type == UPDATETUP ||
pXlaRecord->type == DELETETUP)
{
/* Get source table description associated with this record */
/* from the time it was generated. */
rc = ttXlaVersionTableInfo (pCmd->pCtx->con->hXla,
(ttXlaUpdateDesc_t*) pXlaRecord, &tblVerDescSource);
if (rc == SQL_SUCCESS)
{
/* Get the source column descriptors for this table */
/* at the time the record was generated. */
rc = ttXlaVersionColumnInfo (pCmd->pCtx->con->hXla,
(ttXlaUpdateDesc_t*) pXlaRecord,
colDescSource, 255, &iColsReturned);
if (rc == SQL_SUCCESS)
{
/* Check compatibility. */
rc = ttXlaTableCheck (hXlaTarget,
&tblVerDescSource.tblDesc, colDescSource,
&iCompatible);
}
}
}
}
レプリケートを開始する準備の完了後、ttXlaNextUpdateまたはttXlaNextUpdateWait関数を使用してマスター・データベースから更新レコードのバッチを取得し、ttXlaApplyを使用してサブスクライバ・データベースにレコードを書き込みます。この例を次に示します。
例5-20 データベース間での更新のレプリケート
int j;
ttXlaHandle_h h;
SQLINTEGER records;
ttXlaUpdateDesc_t** arry;
do {
/* get up to 15 updates */
rc = ttXlaNextUpdate(h,&arry,15,&records);
if (rc != SQL_SUCCESS) {
/* See "Handling XLA errors" */
}
/* print number of updates returned */
printf("Records returned by ttXlaNextUpdate : %d\n",records);
/* apply the received updates */
for (j=0;j < records;j++) {
ttXlaUpdateDesc_t* p;
p = arry[j];
rc = ttXlaApply(h, p, 0);
if (rc != SQL_SUCCESS){
/* See "Handling XLA errors" and */
/* "Handling timeout and deadlock errors" below */
}
}
/* print number of updates applied */
printf("Records applied successfully : %d\n",records);
} while (records != 0);
|
重要: ネットワーク上、または同じメモリー領域を使用しないプロセス間の任意の場所でレプリケートするデータをパッケージ化する場合は、ttXlaUpdateDesc_tデータ構造体を完全に含める必要があります。その長さはttXlaUpdateDesc_t ->header.lengthで示され、header要素はttXlaNodeHdr_t構造体で、この構造体はlength要素を持ちます。「ttXlaUpdateDesc_t」および「ttXlaNodeHdr_t」も参照してください。 |
ttXlaApplyからのリターン・コードは、更新が正常に行われたかどうかを示します。リターン・コードがSQL_SUCCESSでない場合は、更新で一時的な問題(デッドロック、タイムアウトなど)または永続的な問題が発生した可能性があります。ttXlaErrorを使用して、tt_ErrDeadlockVictim、tt_ErrTimeoutVictimなどのエラーを確認できます。一時的なエラーは、レプリケートしたトランザクションをロールバックし、再実行することでリカバリできます。その他のエラーは、重複キー違反、キーが見つからないなどの永続的エラーである可能性があります。このようなエラーは、トランザクションを再実行しても、繰り返し発生する可能性があります。
ttXlaApplyが、トランザクションのコミット・レコード(ttXlaUpdateDesc_t ->flags = TT_UPDCOMMIT)をサブスクライバ・データベースに適用する前にタイムアウトまたはデッドロック・エラーを返した場合は、次のいずれかの処理を実行できます。
ttXlaRollbackを使用してトランザクションをロールバックします。
ttXlaCommitを使用して、レコードで、サブスクライバ・データベースに適用済の変更をコミットします。
一時的なエラーからのリカバリを有効にするには、マスター・データベースのトランザクション境界を追跡し、サブスクライバに現在適用中のトランザクションに関連付けられているレコードをユーザー・バッファに保存することで、必要に応じてそれらのレコードを再適用できます。トランザクション境界は、ttXlaUpdateDesc_t構造体のflagsメンバーを確認することで検出できます。次の例を想定してください。この条件がtrueの場合、レコードはコミットされています。
(pXlaRecords [iRecordIndex]->flags & TT_UPDCOMMIT)
トランザクションをロールバックする必要があるエラーが発生した場合は、ttXlaRollbackをコールして、サブスクライバ・データベースに適用済のレコードをロールバックします。その後、ttXlaApplyをコールして、バッファに保存されているすべてのロールバック・レコードを再適用します。
|
注意: トランザクション・レコードをユーザー・バッファに保存するかわりに、ttXlaGetLSNをコールしてトランザクション・ログ内の各コミット・レコードのトランザクション・ログ・レコード識別子を取得する方法があります(「ブックマークの位置の変更」を参照)。トランザクションをロールバックする必要があるエラーが発生した場合は、ttXlaSetLSNをコールして、ブックマークをトランザクション・ログ内のトランザクションの先頭に再設定し、レコードを再適用します。ただし、この方法は、ttXlaGetLSN関数関連の追加のオーバーヘッドが発生するため、効率が悪くなる場合があります。 |
アプリケーションで、マスター・データベースおよびサブスクライバ・データベースの両方に対して同時に更新を行うと、更新競合が発生する場合があります。更新競合の詳細は、『Oracle TimesTen In-Memory Database開発者および管理者ガイド』のレプリケーション競合の解消に関する説明を参照してください。
XLAで更新競合を確認するには、ttXlaApplyのtestパラメータを設定して、UPDATETUP型の各レコードのold row値(ttXlaUpdateDesc_t ->tuple1)を、サブスクライバ・データベースの既存の行と比較します。更新記述のold row値がサブスクライバ・データベースの対応する行と一致しない場合は、原因はおそらく更新競合です。この場合、ttXlaApplyはサブスクライバへの更新を適用せず、sb_ErrXlaTupleMismatchエラーを返します。
TimesTen以外のデータベースへの変更をレプリケートする場合は、ttXlaGenerateSQL関数を使用して、レコード・データをTimesTen以外のサブスクライバで読取りが可能なSQL文に変換します。更新および削除のレコードの場合、正しいSQLを生成するために、ttXlaGenerateSQLには、NULL値可能でない列に対する主キー索引または一意索引が必要です。
ttXlaGenerateSQL関数は、パラメータとしてttXlaUpdateDesc_tレコードを受け入れ、それと同等のSQLをバッファに出力します。
|
重要: ttXlaGenerateSQLによって返されるSQLでは、TimesTen SQL構文を使用します。2つのシステム間でSQL構文に互換性がない場合、TimesTen以外のサブスクライバではSQL文が失敗することがあります。さらに、SQL文は、XLAハンドルに関連付けられている接続キャラクタ・セットでエンコードされます。 |
例5-21 TimesTen以外のデータベースへの更新のレプリケート
この例では、レコード(record)を変換し、その結果のSQL出力を200文字のバッファ(buffer)に保存します。バッファの実際のサイズはactualLengthパラメータによって返されます。
ttXlaUpdateDesc_t record;
char buffer[200];
SQLINTEGER actualLength;
rc = ttXlaGenerateSQL(xla_handle, &record, buffer, 200, &actualLength);
if (rc != SQL_SUCCESS) {
handleXLAerror (rc, xla_handle, err_buf, &native_error);
if ( native_error == 8034 ) { // tt_ErrXlaNoSQL
printf("Unable to translate to SQL\n");
}
}
その他のXLA機能の使用方法について説明します。内容は次のとおりです。
接続中は、随時ttXlaGetLSN関数をコールして現行読取りログ・レコード識別子に関してシステムに問い合せることができます。一連の更新を繰り返し実行する必要がある場合は、ttXlaSetLSN関数を使用して、現行読取りログ・レコード識別子を最後のttXlaAcknowledgeコールによって設定された初期読取りログ・レコード識別子より大きい有効な値に再設定できます。ここで、「大きい」とは、比較対象のログ・レコード識別子が同じトランザクション内のレコードのものである場合にのみ意味を持ちます。そうでない場合、ログ・レコード識別子の数値が大きいとしても、別のトランザクションより前にコミットされたトランザクションのログ・レコード識別子は「より小さい」ログ・レコード識別子になります。初期読取りログ・レコード識別子を現行読取りログ・レコード識別子まで進める方法は、ttXlaAcknowledge関数をコールする方法のみで、この関数によって、現行読取りログ・レコード識別子までのすべてのトランザクション・ログ・レコードを取得および処理したということが示されます。特定のブックマークに対してttXlaAcknowledgeをコールすると、現行読取りログ・レコード識別子より小さいログ・レコード識別子を持つトランザクション・ログ・レコードにアクセスできなくなります。
XLA関数ではありませんが、トランザクション・ログに対するライターでttApplicationContext組込みプロシージャをコールして、アプリケーションに関連付けられているバイナリ・データをXLAリーダーに渡すことができます。このプロシージャは、現在のトランザクションが生成する次の更新レコードに返す1つのVARBINARY値を指定します。XLAリーダーでは、「NOT INLINE可変長列データの読取り」で説明されている方法で、この値へのポインタを取得できます。
|
注意: コンテキスト値は、1つの更新レコードにのみ適用されます。適用後に値が再設定されます。同じコンテキスト値を複数の更新に適用する場合は、各更新の前に再設定する必要があります。 |
コンテキストを設定するには、次の手順を実行します。
ttApplicationContextプロシージャを起動するために2つのプログラム変数を宣言します。変数contextBufferは、最も長いアプリケーション・コンテキストを格納できる十分な大きさを持つように宣言されたCHAR配列です。contextBufferLenは、INTEGER型の変数で、ttApplicationContextへの各コールでコンテキストの実際の長さを伝えるために使用されます。
ttApplicationContext組込みプロシージャのコンパイル済の起動によって文ハンドルを初期化します。
rc = SQLPrepare(hstmt, "call ttApplicationContext(?)", SQL_NTS);
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY,
SQL_VARBINARY, 0, 0, &contextBuffer,
sizeof contextBuffer, &contextBufferLen);
アプリケーション・コンテキストを後で設定する必要がある場合は、コンテキスト値をcontextBufferにコピーし、コンテキストの長さをcontextBufferLenに割り当て、次のコールを使用してttApplicationContextを起動します。
rc = SQLExecute(hstmt);
トランザクションは、SQLTransactへの通常のコールでコミットされます。
rc = SQLTransact(NULL, hdbc, SQL_COMMIT);
|
注意: ttApplicationContextをコールした後にSQL処理に失敗すると、コンテキストが次のSQL処理に渡されず、失われる可能性があります。このような問題が発生した場合は、アプリケーションで、次のSQL処理の前にttApplicationContextを再度コールできます。 |