XLAイベント・ハンドラ・アプリケーションの作成
データベース内の選択した表に対する変更を検出しレポートするXLAアプリケーションの作成のための、一般的な手順を説明します。
この項では、「列データの確認」のような例外もありますが、一般的な手順を示します。この項で説明する手順は、ほぼすべてのXLAアプリケーションに当てはまります。
この項で説明する手順は次のとおりです。
この項のコード例は、xlaSimple
サンプル・アプリケーションに基づいています。
ここで説明するXLA関数については、XLAリファレンスを参照してください。
ヒント:
「TimesTenのインクルード・ファイル」で説明したファイルの他に、XLAアプリケーションにはtt_xla.h
をインクルードする必要があります。
ノート:
コード例をわかりやすくするために、各関数コールのルーチン・エラー・チェック・コードは省略されています。「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);
XLAの初期化およびXLAハンドルの取得
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のサポートは、次のように制限されています。
-
LOB列を含む表にサブスクライブできますが、LOB値自体についての情報は使用できません。
-
ttXlaGetColumnInfo
では、LOB列に関する情報が返されます。 -
LOBを含む列は、空(長さがゼロ)またはNULL(実際に値が
NULL
の場合)としてレポートされます。このようにして、NULL列と非NULL列を区別できます。
この例では、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
関数をコールして、トランザクション・ログから一連のレコードを返すことができます。
「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
をコールする前に、複数のトランザクションのレコードを読み取る可能性があります。
この例のxlaSimple
アプリケーションでは、表の更新の監視は、ユーザーが停止するまで続行されます。
この例では、ttXlaNextUpdateWait
をコールする前に、返されるttXlaUpdateDesc_t
レコードを保持するバッファへのポインタ(arry
)と実際に返されるレコード数を保持する変数(records
)を初期化します。この例ではttXlaNextUpdateWait
をコールするため、トランザクション・ログ・バッファでレコードが見つからない場合に待機する秒数(FETCH_WAIT_SECS
)も指定します。
次に、ttXlaNextUpdateWait
をコールし、これらの値を渡してarry
内の一連のttXlaUpdateDesc_t
レコードを取得します。次に、「レコード・ヘッダーの確認および行アドレスの検出」内の例で説明されているように、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 XLA Error Handling */ } /* 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 XLA Error Handling */ } } /* 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));
これを次の例に示します。
ttXlaUpdateDesc_t ->
type
フィールドは、更新を生成したSQL処理のタイプについての記述です。UPDATETTUP
型のトランザクション・レコードは、UPDATE
処理についての記述であるため、更新の前後の行データをレポートするために2行の行を返します。更新後の値が含まれている2番目に返された行のアドレスは、レコード内の1つ目の行のアドレスにその長さに追加することで特定できます。
if (ttXlaUpdateDesc_t->type == UPDATETUP) { tup2 = (void*) ((char*) tup1 + ttXlaUpdateDesc_t->tuple1); }
これも次の例で示します。ここでは、ttXlaNextUpdateWait
関数によって返された各レコードをHandleChange()
関数に渡し、この関数で、そのレコードがINSERT
、UPDATE
またはCREATE VIEW
処理に関連しているかどうかを判別しています。この例では、他のすべての処理は無視されます。
HandleChange()
関数により、PrintColValues()
関数(「PrintColValues()関数ですべてを組み合せる」内の例を参照)をコールする前に、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 is for the table we are 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) */ }
列データの確認
列データを確認する方法について説明します。
更新レコードで返されるデータ
ttXlaUpdateDesc_t
構造体の後に、1つの更新レコードで0行から2行のデータが返される可能性があります。
「ttXlaUpdateDesc_t」およびレコード・ヘッダーの検出と行アドレスの検索を参照してください。
図5-8に示すように、各行のデータの最初の部分は固定長で、その後に可変長データが続きます。
列記述の取得
返された行から列値を読み取るには、まず、その行の各列のオフセットを判別する必要があります。
列オフセットおよびその他の列メタデータは、特定の表に対してttXlaGetColumnInfo
関数をコールすると取得でき、この関数は表の列ごとに個別のttXlaColDesc_t
構造体を返します。ttXlaGetColumnInfo
関数は、初期化プロシージャの一部としてコールする必要があります。「XLAの初期化およびXLAハンドルの取得」の説明では、わかりやすくするためにこのコールは省略されています。
ttXlaGetColumnInfo
をコールする場合は、colinfo
パラメータを指定して、返されるttXlaColDesc_t
構造体のリストを保持するバッファへのポインタを作成します。maxcols
パラメータを使用して、バッファのサイズを定義します。
次に示す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 "XLA Error Handling" */
}
図5-9に示すように、ttXlaGetColumnInfo
関数は次の情報を出力します。
-
ttXlaGetColumnInfo
のcolinfo
パラメータで指すバッファに格納されるttXlaColDesc_t
構造体のリスト(xla_column_defs
) -
xla_column_defs
バッファに実際に返される列の数を保持するnreturned
の値(ncols
)
図5-9 ttXlaGetColumnInfoによって返されるttXlaColDesc_t構造体
「図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値に行のアドレスを追加したものです。
「ttXlaColDesc_t」を参照してください。
ここで示した計算などの機能する完成例は、「PrintColValues()関数ですべてを組み合せる」を参照してください。
MYDATA
表の最初の列はCHAR
型です。HandleChange()
関数で前に取得したtup1
行のアドレス(「レコード・ヘッダーの確認および行アドレスの検出」内の例を参照)と、ttXlaGetColumnInfo
関数によって返された最初の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文字の配列を指します。「PrintColValues()関数ですべてを組み合せる」内の例のSQL_WCHAR
の場合のように、この配列内の各要素に対して繰り返し、文字列を取得する必要があります。
その他の固定長データ型は、対応するCの型にキャストできます。複合固定長データ型(DATE
、TIME
、DECIMAL
値など)は、TimesTenの内部形式で格納されますが、アプリケーションでXLA変換関数を使用して、それらに対応するODBC C値に変換できます(「複合データ型の変換」を参照)。
ノート:
XLAによって返される文字列は空文字では終了しません。「返された文字列の空文字での終了」を参照してください。
NOT INLINE可変長列データの読取り
NOT INLINE
可変長データ(VARCHAR
、NVARCHAR
およびVARBINARY
)の場合、ttXlaColDesc_t ->
offset
にあるデータは、返された行の可変長部分でのデータの場所を指す4バイトのオフセット値です。
オフセット・アドレスをオフセット値に追加すると、行の可変長部分の列データのアドレスを取得できます。この位置にある最初の8バイトはデータの長さで、その後に実際のデータが続きます。可変長データの場合、ttXlaColDesc_t ->
size
値は列の許容最大サイズです。図5-11に、行内のNOT INLINE
可変長データを検出する方法を示します。
ここで示した計算などの機能する完成例は、「PrintColValues()関数ですべてを組み合せる」を参照してください。
引き続きこの例では、返された行(tup1
)の2番目の列はVARCHAR
型です。行内の可変長データを探すには、まず前述の図5-11で示すように、行の固定長部分で列のttXlaColDesc_t ->
offset
の値をみつけます。このアドレスの値は、行の可変長部分のデータの4バイトのオフセットです(VarOffset
)。次に、VarOffset
のアドレスにVarOffset
オフセット値を追加して、可変長列データの先頭へのポインタを取得します(DataLength
)。DataLength
の位置にある最初の8バイトはデータの長さです。DataLength
の後の次のバイトが、実際のデータの先頭です(Column2
)。
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
の場合と同様に値を取得し、その後、「PrintColValues()関数ですべてを組み合せる」内のNCHAR
値CharBuf
の場合と同様に、Column2
配列にわたり繰り返すことができます。
ノート:
前述の例では、DataLength
はデータ型long
で、UNIXベースの64ビット・システムでは64ビット(8バイト)型です。Windows 64ビット・システムでは、long
は4バイト型であり、8バイト型の__int64
がかわりに使用されます。
返された文字列の空文字での終了
レコードの行データから返される文字列は空文字では終了していません。文字列をバッファにコピーし、その文字列の最後の文字の後に\0
などの空文字を追加すると、文字列を空文字で終了できます。
文字列を空文字で終了する手順は、文字列が固定長の場合と可変長の場合で少し異なります。次の各例では、NULLで終了する固定長文字列、NULLで終了する既知のサイズの可変長文字列、およびNULLで終了するサイズ不明の可変長文字列の処理を示します。
ここで示した計算などの機能する完成例は、「PrintColValues()関数ですべてを組み合せる」を参照してください。
「固定長列データの読取り」内の例で返された固定長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可変長列データの読取り」を参照)。
「NOT INLINE可変長列データの読取り」内の例で取得した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);
すべてのデータ型を読み取るための汎用コードを記述する場合は、返される文字列のサイズを想定できません。サイズがわからない文字列に対しては、返された文字列の大部分を保持できる十分な大きさのバッファを静的に割り当てます。返された文字列がバッファより大きい場合は、すぐ下の例で示すように、正しいサイズのバッファを動的に割り当てます。
「NOT INLINE可変長列データの読取り」内の例で取得したColumn2
文字列のサイズがわからない場合は、最大10,000文字の文字列を保持できる十分な大きさのバッファを静的に割り当てます。その後、可変長データ・オフセットの先頭で取得した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);
複合データ型の変換
複合データ型を変換する方法について説明します。
ここで示した計算などの機能する完成例は、「PrintColValues()関数ですべてを組み合せる」を参照してください。
TT_DATE
やTT_TIME
などの複合データ型の値は、TimesTenの内部形式で保存されます。これらは、XLA型変換関数(「XLAデータ型変換関数」を参照)を使用して、対応するODBC Cデータ型に変換できます。
HandleChange()
関数で前に取得したtup1
行のアドレス(「レコード・ヘッダーの確認および行アドレスの検出」内の例を参照)と、ttXlaGetColumnInfo
関数によって返された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 XLA Error Handling */ } 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()
関数で取得したtup1
行のアドレス、およびttXlaGetColumnInfo
関数によって返された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 XLA Error Handling */ } printf(" %s: %s\n", ((unsigned char*) xla_column_defs[5].colName), decimalData);
NULL値の検出
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
)のどちらであるかを確認します。
Column6
がNULLかどうかを次のように確認します。
if (xla_column_defs[5].nullOffset != 0) { if (*((unsigned char*) tup + xla_column_defs[5].nullOffset) == 1) { printf("Column6 is NULL\n"); } }
XLAデータ型変換関数
この項では、TimesTenの内部形式からODBCのCデータ型に変換する、XLAデータ型変換関数を示します。
これらの変換関数は、ttXlaUpdateDesc_t
型(UPDATETUP
、INSERTTUP
およびDELETETUP
)に含まれる行データで使用できます。
表5-2 XLAデータ型変換関数
関数 | 変換 |
---|---|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
|
内部 |
PrintColValues()関数ですべてを組み合せる
各列のttXlaColDesc_t ->
dataType
を確認して、データ型がCHAR
、NCHAR
、INTEGER
、TIMESTAMP
、DECIMAL
およびVARCHAR
である列を検出し、その値を出力する関数を示します。
これは、考えられる1つの方法にすぎません。たとえば、別の方法として、ttXlaColDesc_t ->
ColName
の値を確認して、名前で特定の列をみつける方法もあります。
PrintColValues()
関数は、長さが最大50バイトのCHAR
およびVARCHAR
の文字列を処理します。NCHAR
文字は、ASCII文字セットに属している必要があります。
この例の関数は、まず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 */ }
ノート:
-
前述の例では、
var_len
はデータ型long
で、UNIXベースの64ビット・システムでは64ビット(8バイト)型です。Windows 64ビット・システムでは、long
は4バイト型であり、__int64
がかわりに使用されます。 -
TerminateGracefully()
メソッドのサンプルは、「XLAアプリケーションの終了」を参照してください。
XLAエラー処理
この項では、XLAのエラーの処理について説明します。
XLAエラーとコード
この項では、XLAエラーとそのエラー・コードについて説明します。
表5-3 XLAエラーとコード
エラー | コード |
---|---|
|
802 (一時的) |
|
6001 (一時的) |
|
6002 (一時的) |
|
6003 (一時的) |
|
6220 (一時的) |
|
6221 (一時的) |
|
8024 |
|
8029 |
|
8031 |
|
8034 |
|
8035 |
|
8036 |
|
8037 |
|
8038 |
|
8046 |
|
8047 |
XLAエラーの対処方法
ODBCまたはXLA関数をコールするたびに、エラーのリターン・コードを確認する必要があります。エラーが致命的な場合は、プログラムを終了します。
「XLAアプリケーションの終了」を参照してください。
アプリケーションによっては、前述の項の表5-3で示したような特定のXLAエラーへの対処が必要な場合があります。
エラーは、エラー・コード(エラー番号)またはtt_Err
文字列のどちらかを使用して確認できます。TimesTenエラー・コードとエラー文字列をすべて示すリストは、timesten_home
/install/include/tt_errCode.h
ファイルを参照してください。各メッセージの説明は、『Oracle TimesTen In-Memory Databaseエラー・メッセージおよびSNMPトラップ』のエラーおよび警告のリストを参照してください。
XLA関数のリターン・コードがSQL_SUCCESS
でない場合は、ttXlaError
関数を使用して、XLAハンドルでXLA固有のエラーを取得します。
「エラーのチェック」も参照してください。
次の例では、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
関数をコールしてエラー・スタックのポインタを最初のエラーに再設定します。(SQL_NO_DATA_FOUND
は、sqlext.h
(timesten.h
に含まれています)に定義されています。)
エラー・スタックは、ttXlaError
またはttXlaErrorRestart
以外のすべてのXLA関数へのコール後に消去されます。
ノート:
ttXlaPersistOpen
がXLAハンドルを作成できない場合は、エラー・コードSQL_INVALID_HANDLE
を返します。XLAハンドルが作成されていないため、ttXlaError
を使用してこのエラーを検出することはできません。SQL_INVALID_HANDLE
は、メモリーが割り当てることができない場合または指定したパラメータが無効である場合にのみ返されます。
次の例では、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アプリケーションが接続されており、削除する表を追跡しているブックマークを使用している場合は、次のタスクを実行します。
-
各XLAアプリケーションで
ttXlaTableStatus
関数をコールし、newstatus
パラメータを0に設定する必要があります。これにより、アプリケーションで使用中のXLAブックマークから表がサブスクライブ解除されます。 -
表を削除します。
XLAアプリケーションが接続されておらず、削除する表に関連付けられているブックマークを使用している場合は、次のタスクを実行します。
SYS.XLASUBSCRIPTIONS
システム表を問い合せて、削除する表にサブスクライブされているブックマークを確認します。ttXlaUnsubscribe
組込みプロシージャを使用して、表にサブスクライブされている各XLAブックマークから表をサブスクライブ解除します。- 表を削除します。
ブックマークを削除すると、XLAブックマークからも表がサブスクライブ解除されます。「ブックマークの削除」を参照してください。
ブックマークの削除
アプリケーションを終了する場合や表を削除する場合に、ブックマークを削除することがあります。
アプリケーションが接続されており、ブックマークを使用している場合は、ttXlaDeleteBookmark
関数を使用してXLAブックマークを削除します。
ブックマークは、前の接続がクローズされた後に新しい接続で再利用される可能性があります(「XLAブックマークについて」を参照)。新しい接続は、前の接続が停止した場所からトランザクション・ログの読取りを再開します。次の点に留意してください:
-
ブックマークを削除すると、後続のチェックポイント処理(組込みプロシージャ
ttCkpt
、ttCkptBlocking
など)によって、トランザクション・ログ内の未読の更新レコードに関連するファイル・システム領域が解放されます。 -
ブックマークを削除しない場合、XLAアプリケーションは、接続してブックマークを再利用する際に、プログラムの終了以降累積していたすべての未読の更新レコードを読み取ります。これは、更新レコードがTimesTenトランザクション・ログで永続的であるためです。ただし、これらの未読のレコードがトランザクション・ログ・ファイルに累積し、大量のファイル・システム領域を使用すると危険です。
ノート:
-
レプリケーション・エージェントの実行中は、レプリケートされたブックマークを削除できません。
-
ブックマークを再利用する場合は、トランザクション・ログ・ファイル内の初期読取りログ・レコード識別子から開始します。ブックマークを再利用する接続で、前回、接続を切断した場所から読取りが開始されるようにするには、接続を切断する前に
ttXlaAcknowledge
をコールして、ブックマークの位置を現在アクセスしているレコードに再設定する必要があります。 -
『Oracle TimesTen In-Memory Databaseリファレンス』のttLogHoldsを参照してください。TimesTen組込みプロシージャは、トランザクション・ログの保留に関する情報を返します。
-
ttCkpt
とttCkptBlocking
にはADMIN
権限が必要です。TimesTenの組込みプロシージャと必要な権限については、『Oracle TimesTen In-Memory Databaseリファレンス』の組込みプロシージャを参照してください。
次の例に示すように、xlaSimple
アプリケーションのInitHandler()
関数は、終了時にXLAブックマークを削除します。
if (deleteBookmark) { ttXlaDeleteBookmark(xla_handle); if (rc != SQL_SUCCESS) { /* See XLA Error Handling */ } 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ブックマークを使用している場合、次のいずれかの方法でブックマークを削除できます。
-
ブックマークをクローズして
ttXlaBookmarkDelete
組込みプロシージャをコールします。 -
ブックマークをクローズして
ttIsql
のxladeletebookmark
コマンドを使用します。
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接続ハンドルより先に割り当てられているため、クリーンアップでは、接続ハンドルを環境ハンドルより先に解放します。
この例では、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); }