13 OCIでのユーザー定義コールバック関数
Oracle Call Interfaceでは、OCIコールの他に、ユーザー固有のコードを実行できます。
この機能は次のことに使用できます。
-
ユーザーがアプリケーションをチューニングできるように、トレースやパフォーマンス測定のためのコードを追加します。
-
特定のOCIコールに対するコードの事前処理または事後処理を実行します。
-
Oracle Database固有のOCIインタフェースを使用して、OCI付きの他のデータ・ソースにアクセスし、OCIコールがOracle以外のデータ・ソースへのユーザー・コールバックを使用するようにします。
OCIコールバック機能は、OCIコールの実行前後におけるユーザー・コードのコールをサポートしています。また、OCIコードを実行するかわりに、ユーザー定義コードを実行できる機能も用意されています。
アプリケーションのソース・コードを修正することなく、ユーザー・コールバック・コードを動的に登録できます。動的な登録は、OCIEnvCreate()コール時に環境ハンドルを初期化した後、ユーザーが作成した5つ以下の動的リンク・ライブラリをロードすることによって実装されます。ユーザーが作成したライブラリ(Windowsの動的リンク・ライブラリ(DLL)またはSolaris上の共有ライブラリなど)では、選択したOCIコールに対するユーザー・コールバックをアプリケーションに透過的に登録します。
サンプル・アプリケーション
OCIユーザー・コールバック機能を説明するすべてのデモ・プログラムのリストについては、付録Bを参照してください。
関連項目:
13.1 OCIでのユーザー・コールバックの登録について
アプリケーションから、OCIUserCallbackRegister()関数を使用してユーザー・コールバック・ライブラリを登録できます。
コールバックは、環境ハンドルのコンテキスト内に登録されます。アプリケーションでは、OCIUserCallbackGet()関数を使用してハンドルに登録されたコールバックについての情報を取り出すことができます。
ユーザー定義コールバックは、OCIコールおよび環境ハンドルに対して登録されたサブルーチンです。ユーザー定義コールバックには、最初のコールバック、置換コールバックまたは最後のコールバックを指定できます。
-
最初のコールバックの場合は、プログラムからOCI関数をコールするときにコールされます。
-
置換コールバックは、最初のコールバック実行の後に実行されます。置換コールバックから
OCI_CONTINUEの値が戻されると、後続の置換コールバックまたは通常のOCIコードが実行されます。置換コールバックからOCI_CONTINUE以外の値が戻される場合は、後続の置換コールバックおよびOCIコードは実行されません。 -
置換コールバックから
OCI_CONTINUE以外の値が戻されるか、またはOCI関数が正常に実行されると、プログラムの制御は最後のコールバックに移ります(最後のコールバックが登録されている場合)。
置換コールバックまたは最後のコールバックからOCI_CONTINUE以外が戻された場合は、コールバックからのリターン・コードは、対応付けられたOCIコールから戻されます。
ユーザー・コールバックでは、無効なハンドルまたは無効なコンテキストが渡された場合に、OCI_INVALID_HANDLEを戻すことがあります。
ノート:
任意のコールバックからOCI_CONTINUE以外の値が戻された場合は、リターン・コードが後続のコールバックに渡されます。置換コールバックまたは最後のコールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、最後の(OCI_CONTINUEではない)リターン・コードがOCIコールから戻されます。
13.1.1 OCIUserCallbackRegister
ユーザー・コールバックの登録には、OCIUserCallbackRegister()コールを使用します。
現段階では、OCIUserCallbackRegister()は環境ハンドルでしか登録できません。typedef OCIUserCallbackのユーザー・コールバック関数は、OCI関数コードfcodeによって識別されるOCIコールのコンテキストとともに登録します。コールバックの型は、最初のコールバック、置換または最後のコールバックのいずれの場合もwhenパラメータによって指定します。
たとえば、最初のコールバック関数stmtprep_entry_dyncbk_fnとそのコンテキストdynamic_contextは、次のパラメータを使用してOCIUserCallbackRegister()関数をコールすることにより、OCIStmtPrepare2()コールの環境ハンドルhndlpに対して登録されます。
OCIUserCallbackRegister( hndlp,
OCI_HTYPE_ENV,
errh,
stmtprep_entry_dyncbk_fn,
dynamic_context,
OCI_FNCODE_STMTPREPARE,
OCI_UCBTYPE_ENTRY
(OCIUcb*) NULL);
13.1.2 ユーザー・コールバック関数
ユーザー・コールバック関数について詳しく説明します。
ユーザー・コールバック関数は、次の構文を使用する必要があります。
typedef sword (*OCIUserCallback)
(void *ctxp, /* context for the user callback*/
void *hndlp, /* handle for the callback, env handle for now */
ub4 type, /* type of handlp, OCI_HTYPE_ENV for this release */
ub4 fcode, /* function code of the OCI call */
ub1 when, /* type of the callback, entry or exit */
sword returnCode, /* OCI return code */
ub4 *errnop, /* Oracle error number */
va_list arglist); /* parameters of the oci call */
コールバックは、OCIUserCallbackRegister()コールで説明したパラメータの他に、リターン・コードerrnop、およびプロトタイプ定義で宣言された元のOCIのすべてのパラメータを使用してコールします。
最初のコールバックでは、リターン・コードは常にOCI_SUCCESSとして渡され、*errnopは常に0として渡されます。errnopはIN/OUTパラメータであるため、*errnopはerrnopの内容を指します。
コールバックがOCIリターン・コードを変更しない場合は、OCI_CONTINUEを戻す必要があり、*errnopに戻される値は無視されます。ただし、コールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、最後に戻されたリターン・コードがそのコールのリターン・コードになります。この時点で、*errnopに戻された値は、エラー・ハンドルに設定されるか、または環境ハンドルにエラー情報が戻された場合は環境ハンドルに設定されます。これは、OCIHandleAlloc()などの特定のOCIコールではエラー・ハンドルが存在しないためです。
置換コールバックの場合は、returnCodeは前のコールバックまたはOCIコールから戻されるOCI_CONTINUE以外のリターン・コードであり、*errnopはエラー・ハンドル内に戻されるエラー番号の値です。これによって、後続のコールバックでは、必要に応じてリターン・コードやエラー情報を変更できます。
置換コールバックからOCI_CONTINUE以外のリターン・コードが戻されると、後続の置換コールバックおよびOCIコードは無視され、最後のコールバックの処理に移るという点で、置換コールバックの処理は異なります。
置換コールバックからOCI_CONTINUEが戻されてOCIコードの処理が可能になった場合は、最初のコールバックからのリターン・コードが無視されることに注意してください。
OCIコールの元のパラメータはすべて、変数パラメータとしてコールバックに渡され、コールバックでは、それらをva_argを使用して取り出す必要があります。コールバックのデモ・プログラムには例が提供されています。
コールバックの登録を解除するために、NULL値を登録することができます。つまり、OCIUserCallbackRegister()コールのコールバック(OCIUserCallback())の値がNULLの場合は、ユーザー・コールバックの登録は解除されます。
スレッドセーフ・モードを使用している場合、OCIプログラムでは、ユーザー・コールバックをコールする前にすべてのmutexを取得します。
13.1.3 ユーザー・コールバックの制御フロー
ユーザー・コールバックの制御フローを示します。
例13-1では、典型的なOCIコールの全体的な処理を説明する擬似コードを示しています。
例13-1 典型的なOCIコールの全体的な処理を説明する擬似コード
OCIXyzCall()
{
Acquire mutexes on handles;
retCode = OCI_SUCCESS;
errno = 0;
for all ENTRY callbacks do
{
EntryretCode = (*entryCallback)(..., retcode, &errno, ...);
if (retCode != OCI_CONTINUE)
{
set errno in error handle or environment handle;
retCode = EntryretCode;
}
}
for all REPLACEMENT callbacks do
{
retCode = (*replacementCallback) (..., retcode, &errno, ...);
if (retCode != OCI_CONTINUE)
{
set errno in error handle or environment handle
goto executeEXITCallback;
}
}
retCode = return code for XyzCall; /* normal processing of OCI call */
errno = error number from error handle or env handle;
executeExitCallback:
for all EXIT callbacks do
{
exitRetCode = (*exitCallback)(..., retCode, &errno,...);
if (exitRetCode != OCI_CONTINUE)
{
set errno in error handle or environment handle;
retCode = exitRetCode;
}
}
release mutexes;
return retCode
}
13.1.4 OCIErrorGet()のユーザー・コールバック
OCIコード全体がコールバックに置き換わる場合、コールバックでは、通常、コール・コンテキストに独自のエラー情報を維持し、これを使用して、OCIErrorGet()コールの置換コールバックのbufpパラメータおよびerrcodepパラメータにエラー情報を戻します。
ただし、コールバックがOCIコードの一部をオーバーライドするか、他のなんらかの後処理のみを行う場合は、最後のコールバックを使用して、エラー・テキストや OCIErrorGet()コールのerrcodepパラメータを、独自のエラー・メッセージやエラー番号で修正できます。最後のコールバックに渡される*errnopは、エラー・ハンドルまたは環境ハンドル内のエラー番号です。
関連項目:
13.1.5 最初のコールバックのエラー
最初のコールバックからOCIコールのコール元にエラーを戻す場合は、置換コールバックまたは最後のコールバックを登録する必要があります。
これは、OCIコードが実行される場合、最初のコールバックのエラー・コードが無視されるためです。したがって、最初のコールバックから独自のコンテキストによって、置換コールバックまたは最後のコールバックにエラーが渡される必要があります。
13.1.6 動的なコールバック登録
ユーザー・コールバックは、OCI動作の監視または他のデータ・ソースへアクセスするために使用されることが多いので、コールバックの登録は、割込みが発生しないよう透過的に行われることが望まれます。
これは、OCIの初期化時に、ユーザーが作成した動的リンク・ライブラリをロードすることで可能になります。動的にリンクされているこれらのライブラリはパッケージと呼ばれます。ユーザーが作成したパッケージは、選択したOCIコール用のユーザー・コールバックを登録します。これらのコールバックでは、実行時に制御を受け取ったときに、必要に応じてユーザー・コールバックの追加登録または登録解除を行うことができます。
OCIデモ・プログラムとともに、パッケージを作成するためのMakefile (Solarisのociucb.mk)が用意されています。このパッケージの正確な名前と位置は、オペレーティング・システムによって異なります。パッケージのソース・コードには、OCIの初期化時および環境作成時にコールされる特別なコールバックのコードを含める必要があります。
オペレーティング・システムの環境変数ORA_OCI_UCBPKGを設定することで、パッケージのロードを制御します。この変数によって、汎用的な方法でパッケージに名前が付けられます。パッケージは、$ORACLE_HOME/libディレクトリに格納してください。
13.1.7 複数のパッケージのロードについて
ORA_OCI_UCBPKG変数には、パッケージ名をセミコロンで区切ったリストを含めることができます。パッケージは、リストに指定されている順序でロードされます。
たとえば、以前パッケージは次のように指定されました。
setenv ORA_OCI_UCBPKG mypkg
現在でも前述の指定はできますが、さらに複数のパッケージを次のように指定できます。
setenv ORA_OCI_UCBPKG "mypkg;yourpkg;oraclepkg;sunpkg;msoftpkg"
これらのパッケージはすべて順番にロードされます。つまり、最初にmypkgがロードされ、最後にmsoftpkgがロードされます。
最大5パッケージまで指定できます。
ノート:
サンプルのMakefile ociucb.mkによって、Solarisではociucb.so.1.0、Windowsシステムではociucb.dllが作成されます。ociucbパッケージをロードするには、環境変数ORA_OCI_UCBPKGにociucbを設定する必要があります。パッケージ名が.soで終わっている場合、SolarisではOCIEnvCreate()または OCIEnvNlsCreate()がエラーになります。パッケージ名は、.so.1.0で終わる必要があります。
動的リンク・ライブラリの作成の詳細は、オペレーティング・システムのデモ・ディレクトリにあるMakefileをお読みください。ユーザー定義のコールバックの詳細は、使用されているオペレーティング・システムのドキュメントでアプリケーションのコンパイルとリンクの項を参照してください。
13.1.8 パッケージ・フォーマット
パッケージ・ソースにより2つの関数を指定する必要があります。
以前は、パッケージによりOCIEnvCallback()関数のソース・コードを指定する必要がありました。ただし、OCIEnvCallback()関数は廃止されています。かわりに、パッケージ・ソースにより2つの関数を指定する必要があります。最初の関数の名前はpackagenameにして、Initという接尾辞を付ける必要があります。たとえば、パッケージの名前がfooの場合、ソース・ファイル(たとえば、必ずしもfoo.cというわけではない)には、次のように正確に指定されたOCISharedLibInit()関数のコールが記述されたfooInit()関数が含まれている必要があります。
sword fooInit(metaCtx, libCtx, argfmt, argc, argv)
void * metaCtx; /* The metacontext */
void * libCtx; /* The context for this package. */
ub4 argfmt; /* package argument format */
sword argc; /* package arg count*/
void * argv[]; /* package arguments */
{
return (OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv,
fooEnvCallback));
}
この場合、OCISharedLibInit()関数の最後のパラメータであるfooEnvCallback()は、第2の関数の名前になります。任意の名前にできますが、通常はpackagenameにして、EnvCallbackという接尾辞を付けます。
この関数は、OCIEnvCallback()にかわるものです。現在のところ、すべての動的なユーザー・コールバックは、この関数に登録する必要があります。関数は、次のように指定されるOCIEnvCallbackType型にしてください。
typedef sword (*OCIEnvCallbackType)(OCIEnv *env, ub4 mode,
size_t xtramem_sz, void *usrmemp,
OCIUcb *ucbDesc);
環境ハンドルが作成されると、このコールバック関数が最後にコールされます。envパラメータは、新しく作成された環境ハンドルです。
mode、xtramem_szおよびusrmemppは、OCIEnvCreate()コールに渡されるパラメータです。最後のパラメータucbDescは、パッケージに渡される記述子です。後述するように、パッケージではこの記述子を使用して、ユーザー・コールバックを登録します。
サンプルのociucb.cファイルがdemoディレクトリにあります。Solarisでは、パッケージ作成用のMakefile ociucb.mkも、demoディレクトリに用意されています。これはオペレーティング・システムによって異なりますので、注意してください。さらにdemoディレクトリには、これを説明するユーザー・コールバックのデモ・プログラムがすべて(cdemoucb.c、cdemoucbl.c)揃っています。
13.1.9 ユーザー・コールバックの連鎖
ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。
動的な登録の目的の動作を維持するには、以前に登録されていたコールバックを上書きした後に、上書きしたコールバックを新しく登録したコールバック内でコールするという、アプリケーションのメカニズムが必要です。この結果、ユーザー・コールバックが連鎖することがあります。
OCIUserCallbackGet()関数により、どの関数およびコンテキストをOCIコール用に登録するかが決まります。
関連項目:
13.1.10 OCIを介した他のデータ・ソースへのアクセスについて
Oracle Databaseはアクセス対象となる最も重要なデータベース・ソフトウェアであるため、アプリケーションはOCIインタフェースを有効に利用し、ユーザー・コールバックを使用してOracle以外のデータにアクセスできます。
これにより、OCIで記述されたアプリケーションは、パフォーマンスを低下させずにOracleデータにアクセスできます。ユーザー・コールバックでは、Oracle以外のデータにアクセスするためのドライバを記述できます。OCIには非常に強力なインタフェースが備えられているため、通常、大部分のデータ・ソースに対してOCIコールを直接マッピングできます。このソリューションは、すべてのデータ・ソースに対してパフォーマンスの低下を引き起こす、ODBCのような他の中間層用のアプリケーションを記述することより優れています。OCIを使用するとOracleデータ・ソースにアクセスするのに特に問題は発生しませんが、ODBCがOracle以外のデータ・ソースにアクセスする場合と同じ問題が発生します。
13.1.11 コールバック関数の制限
コールバック関数の制限について詳しく説明します。
OCIEnvCallback()などのコールバック関数の使用方法には、いくつかの制約があります。
-
コールバックからは、
OCIUserCallbackRegister()、OCIUserCallbackGet()、OCIHandleAlloc()およびOCIHandleFree()以外のOCI関数をコールできません。これらの関数であっても、ユーザー・コールバックでコールされている場合、コールバックは再帰を避けるためにコールされません。たとえば、OCIHandleFree()がOCILogoff()のコールバックでコールされている場合は、OCILogoff()のコールバックの実行中にOCIHandleFree()のコールバックを行うことはできません。 -
コールバックでは、環境ハンドルまたはエラー・ハンドルなどのOCIデータ構造を変更できません。
-
OCIUserCallbackRegister()コール自体または次のいずれのコールに対しても、コールバックは登録できません。-
OCIUserCallbackGet() -
OCIEnvCreate() -
OCIInitialize()(非推奨) -
OCIEnvNlsCreate()
-
13.1.12 OCIコールバックの例
OCIコールバックの使用例を示します。
OCIStmtPrepare2()コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。つまり、ORA_OCI_UCBPKG変数が、例13-2のように設定されています。
各パッケージpkgN (Nは1から5)では、pkgNInit()関数およびPkgNEnvCallback()関数を、例13-3のように指定します。
例13-4では、pkgNEnvCallback()関数での最初、置換および最後のコールバックの登録方法を示しています。
最後に、例13-5では、アプリケーションのソース・コードで、ユーザー・コールバックをNULL ucbDescに登録する方法を示しています。
例13-6では、OCIStmtPrepare2()コールが実行されると、コールバックが次の順にコールされることを示しています。
ノート:
最後のコールバックは、最初のコールバックおよび置換コールバックとは逆の順にコールされます。
最初のコールバックと最後のコールバックからは任意のリターン・コードが戻され、続いて次のコールバックが処理されます。ただし、置換コールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、連鎖の次のコールバック(または、最後の置換コールバックの場合はOCIコード)が無視され、最後のコールバックの処理に移ります。たとえば、pkg3_replace_callback_fn()からOCI_SUCCESSが戻された場合、pkg4_replace_callback_fn()、pkg5_replace_callback_fn()およびOCIStmtPrepare2()コールのOCI処理は無視されます。かわりに、pkg5_exit_callback_fn()が次に実行されます。
例13-2 ORA_OCI_UCBPKG変数に対する環境変数の設定
setenv ORA_OCI_UCBPKG "pkg1;pkg2;pkg3;pkg4;pkg5"
例13-3 pkgNInit()関数とPkgNEnvCallback()関数の指定
pkgNInit(void *metaCtx, void *libCtx, ub4 argfmt, sword argc, void **argv)
{
return OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, pkgNEnvCallback);
}
例13-4 pkgNEnvCallback()を使用した最初、置換および最後のコールバックの登録
pkgNEnvCallback(OCIEnv *env, ub4 mode, size_t xtramemsz,
void *usrmemp, OCIUcb *ucbDesc)
{
OCIHandleAlloc((void *)env, (void **)&errh, OCI_HTYPE_ERROR, (size_t) 0,
(void **)NULL);
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_entry_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, ucbDesc);
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_replace_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, ucbDesc);
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_exit_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, ucbDesc);
return OCI_CONTINUE;
}
例13-5 ユーザー・コールバックのNULL ucbDescへの登録
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_entry_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, (OCIUcb *)NULL);
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_replace_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, (OCIUcb *)NULL);
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_exit_callback_fn,
pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, (OCIUcb *)NULL);
例13-6 コールバックを順にコールするためのOCIStmtPrepare()の使用
static_entry_callback_fn()
pkg1_entry_callback_fn()
pkg2_entry_callback_fn()
pkg3_entry_callback_fn()
pkg4_entry_callback_fn()
pkg5_entry_callback_fn()
static_replace_callback_fn()
pkg1_replace_callback_fn()
pkg2_replace_callback_fn()
pkg3_replace_callback_fn()
pkg4_replace_callback_fn()
pkg5_replace_callback_fn()
OCI code for OCIStmtPrepare call
pkg5_exit_callback_fn()
pkg4_exit_callback_fn()
pkg3_exit_callback_fn()
pkg2_exit_callback_fn()
pkg1_exit_callback_fn()
static_exit_callback_fn()
関連項目:
13.2 外部プロシージャからのOCIコールバック
外部プロシージャからのOCIコールバックの使用に関する追加参照情報を提供します。
外部プロシージャからコールバックとして使用できるいくつかのOCI関数があります。
関連項目:
-
外部プロシージャからコールバックとして使用できる関数のリストについては、「OCIカートリッジ関数」を参照してください。
-
PL/SQLコードからコールできるCサブルーチンの作成方法の詳細は、使用可能なOCIコールのリストおよびコード例も含め、『Oracle Database開発ガイド』を参照してください。