この章は、次の項目で構成されています。
この章では、バインドと定義の基本概念をもう一度取り上げ、OCIアプリケーションで使用できる様々な種類のバインドと定義を詳しく説明します。さらに、構造体配列の使用方法、およびバインド、定義、文字変換における諸問題についても説明します。
たとえば、次のようなINSERT
文があるとします。
INSERT INTO emp VALUES (:empno, :ename, :job, :sal, :deptno)
次のように変数を宣言したとします。
text *ename, *job; sword empno, sal, deptno;
バインド・ステップでは、プレースホルダ名とプログラム変数のアドレス間の関連付けを行います。また、図5-1に示すように、バインドでは、プログラム変数のデータ型と長さを指定します。
バインド変数の値のみを変更した場合は、文を再実行するために再バインドする必要はありません。バインドは参照によるため、変数のアドレスおよびハンドルが有効であるかぎり、再バインドせずに、変数を参照する文を再実行できます。
Oracleサーバーでは、名前付きデータ型、REFおよびLOB用のデータ型が実装されています。これらのデータ型は、SQL文中でプレースホルダとしてバインドできます。
注意: 記述子やロケータなど、サイズが不明で不透明なデータ型の場合は、記述子またはロケータ・ポインタのアドレスを渡してください。サイズ・パラメータに、適切なデータ構造のサイズ( sizeof(structure) )を設定します。 |
前の項のSQL文は、名前付きバインドの例です。文の各プレースホルダには、'ename'や'sal'などの名前が関連付けられています。この文を準備し、プレースホルダをアプリケーションの値に関連付けるときは、OCIBindByName()
コールのplaceholderパラメータでプレースホルダの名前を渡し、プレースホルダの名前による関連付けを行います。
もう1つの型のバインドは、定位置バインドです。定位置バインドでは、プレースホルダは名前ではなく文中の位置によって参照されます。このバインドでは、OCIBindByPos()
コールを使用して、入力値とプレースホルダの位置との間の関連付けを行います。
前の項に示した例を定位置バインドに使用すると、次のようになります。
INSERT INTO emp VALUES (:empno, :ename, :job, :sal, :deptno)
5つのプレースホルダは、OCIBindByPos()
をコールし、position
パラメータでプレースホルダの位置番号を渡すことによって、それぞれバインドされます。たとえば、OCIBindByPos()
をコールすることによって、:empno
プレースホルダは位置1に、:ename
は位置2にというようにバインドされます。
重複バインドの場合、1つのバインド・コールのみが必要です。次のSQL文について考えてみます。このSQL文は、コミッションと給料の両方が指定の金額よりも大きい従業員をデータベースで問い合せます。
SELECT empno FROM emp WHERE sal > :some_value AND comm > :some_value
OCIアプリケーションでは、OCIBindByName()
を1回コールするのみで、:some_value
プレースホルダを名前によってバインドし、この文のバインドを完了できます。この場合、2番目のプレースホルダは、最初のプレースホルダからのバインド情報を継承します。
Oracleにデータを渡すには、様々な方法があります。
OCIStmtExecute()
ルーチンを使用してSQL文を繰り返し実行し、反復のたびに異なる入力値を指定することもできます。
Oracle配列インタフェースを使用すると、単一の文とOCIStmtExecute()
のコール1回のみで、多数の値を入力できます。その場合、配列を入力プレースホルダにバインドし、itersパラメータで制御して配列全体を同時に渡すことができます。
配列インタフェースを使用すると、大量のデータを更新または挿入する場合に、データベースとのラウンドトリップの回数を大幅に削減できます。この削減により、通信量の多いクライアント/サーバー環境では、大きなパフォーマンスの向上につながります。たとえば、データベースに10行挿入するアプリケーションを考えてみます。OCIStmtExecute()
を10回、それぞれ1個の値を指定してコールすると、すべてのデータの挿入にネットワーク・ラウンドトリップが10回必要です。入力配列を使用すると、OCIStmtExecute()
を1回コールするのみで同じ結果が得られ、ネットワーク・ラウンドトリップは1回ですみます。
PL/SQLブロックの処理は、単一のSQL文の場合と同じようにそのブロックを文字列変数に入れて、任意の変数をバインドし、ブロックを含む文を実行して行います。
PL/SQLブロックのプレースホルダをプログラム変数にバインドする場合は、OCIBindByName()
またはOCIBindByPos()
を使用して、スカラーまたは配列のいずれかのホスト変数に対して基本バインドを行います。
次の短いPL/SQLブロックには2つのプレースホルダが含まれ、従業員番号と新規給与額に基づき、従業員の給与を更新するプロシージャへのINパラメータを表します。
char plsql_statement[] = "BEGIN\ RAISE_SALARY(:emp_number, :new_sal);\ END;" ;
これらのプレースホルダは、SQL文のプレースホルダと同じ方法で入力変数にバインドできます。
PL/SQL文を処理するとき、出力変数もまたバインド・コールを使用してプログラム変数に関連付けられます。
たとえば、次のようなPL/SQLブロックがあるとします。
BEGIN SELECT ename,sal,comm INTO :emp_name, :salary, :commission FROM emp WHERE empno = :emp_number; END;
OCIBindByName()
を使用して、変数を:emp_name
、:salary
および:commission
の各出力プレースホルダにバインドし、入力プレースホルダ:emp_number
にも変数をバインドします。
プレースホルダは複数のステップでバインドされます。単純なスカラー・バインドまたは配列バインドの場合は、OCIBindByName()
またはOCIBindByPos()
を使用して、プレースホルダとデータとの関連付けを指定するのみです。
バインドが完了すると、SQL文の実行時には、入力データの位置やPL/SQL出力データを入れる位置がOCIライブラリに対して明らかになります。プログラム入力データは、プログラム変数をプレースホルダにバインドする際にプログラム変数内に存在している必要はありませんが、文を実行する際には存在している必要があります。
次のコード例は、SQL文の各プレースホルダのハンドル割当てとバインドを示しています。
... /* The SQL statement, associated with stmthp (the statement handle) by calling OCIStmtPrepare() */ text *insert = (text *) "INSERT INTO emp(empno, ename, job, sal, deptno)\ VALUES (:empno, :ename, :job, :sal, :deptno)"; ... /* Bind the placeholders in the SQL statement, one per bind handle. */ checkerr(errhp, OCIBindByName(stmthp, &bnd1p, errhp, (text *) ":ENAME", strlen(":ENAME"), (ub1 *) ename, enamelen+1, SQLT_STR, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); checkerr(errhp, OCIBindByName(stmthp, &bnd2p, errhp, (text *) ":JOB", strlen(":JOB"), (ub1 *) job, joblen+1, SQLT_STR, (void *) &job_ind, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); checkerr(errhp, OCIBindByName(stmthp, &bnd3p, errhp, (text *) ":SAL", strlen(":SAL"), (ub1 *) &sal, (sword) sizeof(sal), SQLT_INT, (void *) &sal_ind, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); checkerr(errhp, OCIBindByName(stmthp, &bnd4p, errhp, (text *) ":DEPTNO", strlen(":DEPTNO"), (ub1 *) &deptno,(sword) sizeof(deptno), SQLT_INT, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); checkerr(errhp, OCIBindByName(stmthp, &bnd5p, errhp, (text *) ":EMPNO", strlen(":EMPNO"), (ub1 *) &empno, (sword) sizeof(empno), SQLT_INT, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0,OCI_DEFAULT));
OCI中のPL/SQLブロックで最もよく使用されるのは、おそらくストアド・プロシージャまたはストアド・ファンクションのコールです。データベースに格納されたRAISE_SALARYというプロシージャがあるとします。そのプロシージャに対するコールを無名PL/SQLブロックに埋め込み、PL/SQLブロックを処理します。
次に示すプログラムの一部分は、ストアド・プロシージャ・コールをOCIアプリケーションに埋め込む方法の例です。このプログラムでは、raise_salary
というストアド・プロシージャへの入力として、従業員番号と新しい給与が渡されます。
raise_salary (employee_num IN, sal_increase IN, new_salary OUT);
このプロシージャでは、指定された従業員給与に指定された額が上乗せされます。増額された給与はストアド・プロシージャの変数new_salary
に戻され、この値がプログラムで表示されます。
PL/SQL OUT変数をバインドする必要がありますが、PL/SQLプロシージャ引数new_salary
が未定義であることに注意してください。これは、OCI定義の項で詳しく説明します。
/* Define PL/SQL statement to be used in program. */ text *give_raise = (text *) "BEGIN\ RAISE_SALARY(:emp_number,:sal_increase, :new_salary);\ END;"; OCIBind *bnd1p = NULL; /* the first bind handle */ OCIBind *bnd2p = NULL; /* the second bind handle */ OCIBind *bnd3p = NULL; /* the third bind handle */ static void checkerr(); sb4 status; main() { sword empno, raise, new_sal; void *tmp; OCISession *usrhp = (OCISession *)NULL; ... /* attach to database server, and perform necessary initializations and authorizations */ ... /* allocate a statement handle */ checkerr(errhp, OCIHandleAlloc( (void *) envhp, (void **) &stmthp, OCI_HTYPE_STMT, 100, (void **) &tmp)); /* prepare the statement request, passing the PL/SQL text block as the statement to be prepared */ checkerr(errhp, OCIStmtPrepare(stmthp, errhp, (text *) give_raise, (ub4) strlen(give_raise), OCI_NTV_SYNTAX, OCI_DEFAULT)); /* bind each of the placeholders to a program variable */ checkerr( errhp, OCIBindByName(stmthp, &bnd1p, errhp, (text *) ":emp_number", -1, (ub1 *) &empno, (sword) sizeof(empno), SQLT_INT, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); checkerr( errhp, OCIBindByName(stmthp, &bnd2p, errhp, (text *) ":sal_increase", -1, (ub1 *) &raise, (sword) sizeof(raise), SQLT_INT, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); /* remember that PL/SQL OUT variable are bound, not defined */ checkerr( errhp, OCIBindByName(stmthp, &bnd3p, errhp, (text *) ":new_salary", -1, (ub1 *) &new_sal, (sword) sizeof(new_sal), SQLT_INT, (void *) 0, (ub2 *) 0, (ub2) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT)); /* prompt the user for input values */ printf("Enter the employee number: "); scanf("%d", &empno); /* flush the input buffer */ myfflush(); printf("Enter employee's raise: "); scanf("%d", &raise); /* flush the input buffer */ myfflush(); /* execute PL/SQL block*/ checkerr(errhp, OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT)); /* display the new salary, following the raise */ printf("The new salary is %d\n", new_sal); }
この例は、単一のバインド・コールのみが必要な単純なスカラー・バインドを実行する方法を示しています。場合によっては、特定のバインド・データ型または実行モードについて属性を定義するために、追加のバインド・コールが必要です。
「OCIのプレースホルダのバインド」では、OCIBindByName()
またはOCIBindByPos()
を使用して、SQL文のプレースホルダとプログラム変数間の関連付けを行うための基本バインド操作の方法について説明しました。この項では、マルチステップ・バインド、名前付きデータ型のバインド、REF
のバインドなどの拡張バインド操作について説明します。
場合によっては、特定のバインド・データ型または特定の実行モードについて特定の属性を定義するために、追加のバインド・コールが必要です。
以降の項ではこの特別なケースについて説明します。バインドの詳細は、表5-1にまとめてあります。
表5-1 バインド・タイプの情報の概要
バインドのタイプ | バインドのデータ型 | 注意 |
---|---|---|
スカラー |
任意のスカラー・データ型 |
|
スカラーの配列 |
任意のスカラー・データ型 |
|
名前付きデータ型 |
SQLT_NTY |
次の2つのバインド・コールが必要です。
|
REF |
SQLT_REF |
次の2つのバインド・コールが必要です。
|
LOB
|
SQLT_BLOB SQLT_CLOB |
|
構造体配列 または静的配列 |
可変 |
次の2つのバインド・コールが必要です。
|
ピース単位挿入 |
可変 |
|
REF CURSOR変数 |
SQLT_RSET |
文ハンドル、 |
LOBをバインドするには、次の2つの方法があります。
実際のLOB値ではなく、LOBロケータをバインドします。この場合、LOB値の書込みや読取りは、LOBロケータをOCI LOB関数に渡すことによって行われます。
LOBロケータを使用せず、LOB値を直接バインドします。
単一のバインド・コール内で、単一のロケータをバインドすることも、ロケータの配列をバインドすることもできます。いずれの場合も、アプリケーションは、ロケータ自体ではなく、LOBロケータのアドレスを渡す必要があります。たとえば、アプリケーションで、次のようなSQL文を用意したとします。
INSERT INTO some_table VALUES (:one_lob)
このSQL文では、one_lob
が、1つのLOB列に対応するバインド変数です。このとき、次のような宣言をしたとします。
OCILobLocator * one_lob;
次のようなコールを使用して、プレースホルダをバインドし、文を実行します。
/* initialize single locator */ one_lob = OCIDescriptorAlloc(...OCI_DTYPE_LOB...); ... /* pass the address of the locator */ OCIBindByName(...,(void *) &one_lob,... SQLT_CLOB, ...); OCIStmtExecute(...,1,...) /* 1 is the iters parameter */
また、同一のSQL INSERT
文を使用して、配列の挿入ができます。このケースでは、アプリケーション内に次のコードが組み込まれている必要があります。
OCILobLocator * lob_array[10]; ... for (i=0; i<10, i++) lob_array[i] = OCIDescriptorAlloc(...OCI_DTYPE_LOB...); /* initialize array of locators */ ... OCIBindByName(...,(void *) lob_array,...); OCIBindArrayOfStruct(...); OCIStmtExecute(...,10,...); /* 10 is the iters parameter */
記述子は、使用する前にOCIDescriptorAlloc()
ルーチンによって割り当てる必要があります。ロケータの配列の場合は、OCIDescriptorAlloc()
を使用して、各配列要素を初期化する必要があります。BLOB
、CLOB
およびNCLOB
を割り当てる場合は、OCI_DTYPE_LOB
をtype
パラメータとして使用します。BFILE
を割り当てる場合は、OCI_DTYPE_FILE
を使用します。
INSERT
またはUPDATE
のピース単位操作およびコールバック操作は、サポートされていません。
FILEロケータをINSERT
文またはUPDATE
文のバインド変数として使用する場合は、INSERT
文またはUPDATE
文を発行する前に、OCILobFileSetName()
を使用してディレクトリ・オブジェクトおよびファイル名で、ロケータを初期化する必要があります。
0(ゼロ)以外のあらゆるサイズのLOBでINSERT
とUPDATE
に対するバインドが可能です。OCIBindByPos()
、OCIBindByName()
およびPL/SQLのバインドを使用すると、データをLOB列にバインドできます。
4KBを超えるデータをLOB列にバインドする場合は、一時表領域の一部を使用します。一時表領域が少なくともLOBバインドの長さの合計値に等しい量のデータを格納できる大きさであることを確認してください。一時表領域が拡張可能であれば、既存の領域がいっぱいになると自動的に拡張されます。次のコマンドを使用して、拡張できる一時表領域を作成します。
CREATE TABLESPACE ... AUTOEXTENT ON ... TEMPORARY ...;
表にLONG
列とLOB列の両方がある場合は、LONG
列とLOB列のどちらに対しても4KBを超えるデータのバインドが可能ですが、同一の文で両方を実行することはできません。
INSERT
AS
SELECT
操作で、LOB列に対するデータのバインドはできません。
4000バイトを超えるデータに対しては、HEX
からRAW
、RAW
からHEX
などの暗黙的な変換は行われません。次のPL/SQLコードはその例です。
create table t (c1 clob, c2 blob); declare text varchar(32767); binbuf raw(32767); begin text := lpad ('a', 12000, 'a'); binbuf := utl_raw.cast_to_raw(text); -- The following works: insert into t values (text, binbuf); -- The following won't work because Oracle won't do implicit -- hex to raw conversion. insert into t (c2) values (text); -- The following won't work because Oracle won't do implicit -- raw to hex conversion. insert into t (c1) values (binbuf); -- The following won't work because we can't combine the -- utl_raw.cast_to_raw() operator with the >4k bind. insert into t (c2) values (utl_raw.cast_to_raw(text)); end; /
4000バイトを超えるデータをBLOB
またはCLOB
にバインドし、データがSQL演算子によってフィルタされる場合は、結果のサイズが制限されて4000バイト以下になります。
たとえば、次のようにします。
create table t (c1 clob, c2 blob); -- The following command inserts only 4000 bytes because the result of -- LPAD is limited to 4000 bytes insert into t(c1) values (lpad('a', 5000, 'a')); -- The following command inserts only 2000 bytes because the result of -- LPAD is limited to 4000 bytes, and the implicit hex to raw conversion -- converts it to 2000 bytes of RAW data. insert into t(c2) values (lpad('a', 5000, 'a'));
次の例で使用されるSQL文について考えます。
CREATE TABLE foo (a INTEGER ); CREATE TYPE lob_typ AS OBJECT (A1 CLOB ); CREATE TABLE lob_long_tab (C1 CLOB, C2 CLOB, CT3 lob_typ, L LONG);
void insert() /* A function in an OCI program */ { /* The following is allowed */ ub1 buffer[8000]; text *insert_sql = (text *) "INSERT INTO lob_long_tab (C1, C2, L) \ VALUES (:1, :2, :3)"; OCIStmtPrepare(stmthp, errhp, insert_sql, strlen((char*)insert_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[1], errhp, 2, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[2], errhp, 3, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void insert() { /* The following is allowed */ ub1 buffer[8000]; text *insert_sql = (text *) "INSERT INTO lob_long_tab (C1, L) \ VALUES (:1, :2)"; OCIStmtPrepare(stmthp, errhp, insert_sql, strlen((char*)insert_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[1], errhp, 2, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void update() { /* The following is allowed, no matter how many rows it updates */ ub1 buffer[8000]; text *update_sql = (text *)"UPDATE lob_long_tab SET \ C1 = :1, C2=:2, L=:3"; OCIStmtPrepare(stmthp, errhp, update_sql, strlen((char*)update_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[1], errhp, 2, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[2], errhp, 3, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void update() { /* The following is allowed, no matter how many rows it updates */ ub1 buffer[8000]; text *update_sql = (text *)"UPDATE lob_long_tab SET \ C1 = :1, C2=:2, L=:3"; OCIStmtPrepare(stmthp, errhp, update_sql, strlen((char*)update_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[1], errhp, 2, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[2], errhp, 3, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void insert() { /* Piecewise, callback and array insert/update operations similar to * the allowed regular insert/update operations are also allowed */ }
void insert() { /* The following is NOT allowed because we try to insert >4000 bytes * into both LOB and LONG columns */ ub1 buffer[8000]; text *insert_sql = (text *)"INSERT INTO lob_long_tab (C1, L) \ VALUES (:1, :2)"; OCIStmtPrepare(stmthp, errhp, insert_sql, strlen((char*)insert_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[1], errhp, 2, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void insert() { /* Insert of data into LOB attributes is allowed */ ub1 buffer[8000]; text *insert_sql = (text *)"INSERT INTO lob_long_tab (CT3) \ VALUES (lob_typ(:1))"; OCIStmtPrepare(stmthp, errhp, insert_sql, strlen((char*)insert_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 2000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
void insert() { /* The following is NOT allowed because we try to do insert as * select character data into LOB column */ ub1 buffer[8000]; text *insert_sql = (text *)"INSERT INTO lob_long_tab (C1) SELECT \ :1 from FOO"; OCIStmtPrepare(stmthp, errhp, insert_sql, strlen((char*)insert_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIBindByPos(stmthp, &bindhp[0], errhp, 1, (void *)buffer, 8000, SQLT_LNG, 0, 0, 0, 0, 0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *) NULL, (OCISnapshot *) NULL, OCI_DEFAULT); }
OCIBindByName()
またはOCIBindByPos()
コールのmode
パラメータがOCI_DATA_AT_EXEC
に設定されているとき、アプリケーションで実行時にデータを提供するコールバック方式を使用する場合は、追加のOCIBindDynamic()
コールが必要です。OCIBindDynamic()
のコールは、提供されたデータまたはその一部を示すために、必要に応じてコールバック・ルーチンを設定します。OCI_DATA_AT_EXEC
モードを選択した場合でも、コールバックのかわりに標準のOCIのピース単位ポーリング・メソッドを使用する場合は、OCIBindDynamic()
コールは必要ありません。
RETURN
句の変数をバインドするとき、アプリケーションでは、OCI_DATA_AT_EXEC
モードを使用して、コールバックを提供する必要があります。
問合せ文は、データベースのデータをアプリケーションに戻します。問合せを処理するときには、データを検索する選択リストの各項目について、出力変数または出力変数の配列を定義する必要があります。定義ステップでは、戻される結果の格納場所と格納形式を判断する関連付けを行います。
たとえば、プログラムで次の文を処理するには、通常は2つの出力変数を定義する必要があります。1つは、name
列から戻される値を受け取るための変数、もう1つはssn
列から戻される値を受け取るための変数です。
SELECT name, ssn FROM employees WHERE empno = :empnum
name
列の値のみを取り出す場合は、ssn
列用の出力変数を定義する必要はありません。処理されるSELECT
文が問合せに対して複数の行を戻す場合、出力変数はスカラー値のかわりに配列を定義できます。
定義ステップは、アプリケーションに応じて文の実行前または実行後に行います。コンパイル時に選択リスト項目のデータ型がわかっている場合には、文を実行する前に定義を行うことができます。これに対して、動的SQL文(実行時に入力する文など)、あるいは明確に定義された選択リストがない文をアプリケーションで処理する場合、アプリケーションはその文を実行して、記述情報を取り出す必要があります。記述情報を取り出した後、各選択リスト項目の型情報を使用して、出力変数を定義できます。
OCIでは、クライアント側で定義コールをローカルに処理します。定義ステップでは、結果を格納するバッファの位置を指示し、さらに、データがアプリケーションに戻されるときに行う必要があるデータ変換の種類を判断します。
注意: 出力バッファには、2バイトの位置揃えを行う必要があります。 |
OCIDefineByPos()
コールのdty
パラメータでは、出力変数のデータ型を指定します。 OCIでは、データを出力変数にフェッチするときに広範囲のデータ変換を行うことができます。たとえば、Oracle DATE
形式の内部データは、出力時に、自動的にString
データ型に変換できます。
基本的な定義は、定位置コールのOCIDefineByPos()
により行われます。このステップでは、選択リスト項目と出力変数との間の関連付けを行います。特定のデータ型またはフェッチ・モードには追加の定義コールが必要です。定義ステップが完了すると、OCIライブラリでは、データを入れる位置を決定します。SQL文を再準備したり再実行することなく、定義コールを再度行って出力変数を再定義できます。
次の例では、実行と記述の後にスカラー出力変数を定義しています。
SELECT department_name FROM departments WHERE department_id = :dept_input /* The input placeholder was bound earlier, and the data comes from the user input below */ printf("Enter employee dept: "); scanf("%d", &deptno); /* Execute the statement. If OCIStmtExecute() returns OCI_NO_DATA, meaning that no data matches the query, then the department number is invalid. */ if ((status = OCIStmtExecute(svchp, stmthp, errhp, 0, 0, (OCISnapshot *) 0, (OCISnapshot *) 0, OCI_DEFAULT)) && (status != OCI_NO_DATA)) { checkerr(errhp, status); return OCI_ERROR; } if (status == OCI_NO_DATA) { printf("The dept you entered doesn't exist.\n"); return 0; } /* The next two statements describe the select-list item, dname, and return its length */ checkerr(errhp, OCIParamGet((void *)stmthp, (ub4) OCI_HTYPE_STMT, errhp, (void **)&parmdp, (ub4) 1)); checkerr(errhp, OCIAttrGet((void*) parmdp, (ub4) OCI_DTYPE_PARAM, (void*) &deptlen, (ub4 *) &sizelen, (ub4) OCI_ATTR_DATA_SIZE, (OCIError *) errhp )); /* Use the retrieved length of dname to allocate an output buffer, and then define the output variable. If the define call returns an error, exit the application */ dept = (text *) malloc((int) deptlen + 1); if (status = OCIDefineByPos(stmthp, &defnp, errhp, 1, (void *) dept, (sb4) deptlen+1, SQLT_STR, (void *) 0, (ub2 *) 0, (ub2 *) 0, OCI_DEFAULT)) { checkerr(errhp, status); return OCI_ERROR; }
場合によっては、定義ステップではOCIDefineByPos()
のコールのみでなく、他のコールも必要です。配列のフェッチ(OCIDefineArrayOfStruct()
)または名前付きデータ型のフェッチ(OCIDefineObject()
)の属性を定義する追加コールがあります。たとえば、1つの名前付きデータ型列を指定して複数の行をフェッチするには、列に対して3つのコールすべてを呼び出す必要がありますが、スカラー列の複数行をフェッチするには、OCIDefineArrayOfStruct()
とOCIDefineByPos()
で十分です。
また、Oracleには、オブジェクト型属性をマップする事前定義済のCデータ型が用意されています。
この項では、マルチステップ定義、名前付きデータ型定義、REF定義などの拡張定義操作について説明します。
場合によっては、定義ステップでは、配列のフェッチ(OCIDefineArrayOfStruct()
)または名前付きデータ型のフェッチ(OCIDefineObject()
)の属性を定義する追加コールが必要になります。たとえば、1つの名前付きデータ型列を指定して複数の行をフェッチするには、列に対して3つのコールすべてを呼び出す必要がありますが、スカラー列の複数行をフェッチするには、OCIDefineArrayOfStruct()
とOCIDefineByPos()
で十分です。
LOBを定義するには、次の2つの方法があります。
実際のLOB値ではなく、LOBロケータとして定義します。この場合、LOB値の書込みや読取りは、LOBロケータをOCI LOB関数に渡すことによって行われます。
LOBロケータを使用せず、LOB値を直接定義します。
単一の定義コール内では、単一のロケータを定義することも、ロケータの配列を定義することもできます。いずれの場合も、アプリケーションは、ロケータ自体ではなく、LOBロケータのアドレスを渡す必要があります。たとえば、アプリケーションで、次のようなSQL文を準備したとします。
SELECT lob1 FROM some_table;
このSQL文のlob1
はLOB列です。one_lob
は、次の宣言を伴ったLOB列に対応する定義変数です。
OCILobLocator * one_lob;
次のステップは、プレースホルダをバインドし、文を実行します。
/* initialize single locator */ one_lob = OCIDescriptorAlloc(...OCI_DTYPE_LOB...); ... /* pass the address of the locator */ OCIDefineByPos(...,1, ...,(void *) &one_lob,... SQLT_CLOB, ...); OCIStmtExecute(...,1,...); /* 1 is the iters parameter */
また、同一のSQL SELECT
文を使用して、配列を選択できます。このケースでは、アプリケーション内に次のステップが組み込まれている必要があります。
OCILobLocator * lob_array[10]; ... for (i=0; i<10, i++) lob_array[i] = OCIDescriptorAlloc(...OCI_DTYPE_LOB...); /* initialize array of locators */ ... OCIDefineByPos(...,1, (void *) lob_array,... SQLT_CLOB, ...); OCIDefineArrayOfStruct(...); OCIStmtExecute(...,10,...); /* 10 is the iters parameter */
記述子は、使用する前にOCIDescriptorAlloc()
ルーチンによって割り当てる必要があることに注意してください。ロケータの配列の場合は、OCIDescriptorAlloc()
を使用して、各配列要素を初期化する必要があります。BLOB
、CLOB
およびNCLOB
を割り当てる場合は、OCI_DTYPE_LOB
をtype
パラメータとして使用します。BFILE
を割り当てる場合は、OCI_DTYPE_FILE
を使用します。
0(ゼロ)以外のあらゆるサイズのLOBでSELECT
の定義が可能です。OCIDefineByPos()
およびPL/SQLの定義を使用すると、使用可能な最大サイズのデータをLOB列から選択できます。1行に複数のLOBを含めることができるため、同一のSELECT
文中の各LOBに対して最大サイズのデータを選択できます。
次の例で使用されるSQL文について考えます。
CREATE TABLE lob_tab (C1 CLOB, C2 CLOB);
void select_define_before_execute() /* A function in an OCI program */ { /* The following is allowed */ ub1 buffer1[8000]; ub1 buffer2[8000]; text *select_sql = (text *)"SELECT c1, c2 FROM lob_tab"; OCIStmtPrepare(stmthp, errhp, select_sql, (ub4)strlen((char*)select_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIDefineByPos(stmthp, &defhp[0], errhp, 1, (void *)buffer1, 8000, SQLT_LNG, (void *)0, (ub2 *)0, (ub2 *)0, (ub4) OCI_DEFAULT); OCIDefineByPos(stmthp, &defhp[1], errhp, 2, (void *)buffer2, 8000, SQLT_LNG, (void *)0, (ub2 *)0, (ub2 *)0, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 1, 0, (OCISnapshot *)0, (OCISnapshot *)0, OCI_DEFAULT); }
void select_execute_before_define() { /* The following is allowed */ ub1 buffer1[8000]; ub1 buffer2[8000]; text *select_sql = (text *)"SELECT c1, c2 FROM lob_tab"; OCIStmtPrepare(stmthp, errhp, select_sql, (ub4)strlen((char*)select_sql), (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT); OCIStmtExecute(svchp, stmthp, errhp, 0, 0, (OCISnapshot *)0, (OCISnapshot *)0, OCI_DEFAULT); OCIDefineByPos(stmthp, &defhp[0], errhp, 1, (void *)buffer1, 8000, SQLT_LNG, (void *)0, (ub2 *)0, (ub2 *)0, (ub4) OCI_DEFAULT); OCIDefineByPos(stmthp, &defhp[1], errhp, 2, (void *)buffer2, 8000, SQLT_LNG, (void *)0, (ub2 *)0, (ub2 *)0, (ub4) OCI_DEFAULT); OCIStmtFetch(stmthp, errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT); }
構造体配列の定義では、最初にOCIDefineByPos()
をコールする必要があります。構造体配列操作に必要なskip
パラメータなどの各追加パラメータを設定するには、追加のOCIDefineArrayOfStruct()
コールが必要です。
構造体配列の使用により、複数行、複数列の操作の処理を簡素化できます。関連したスカラー・データ項目の構造体を作成し、データベースの値を構造体配列へフェッチしたり、これらの構造体配列の値をデータベースに挿入できます。
たとえば、あるアプリケーションでNAME
、AGE
およびSALARY
という列から、複数行のデータをフェッチする必要があるとします。アプリケーションでは、個別のフィールド(それぞれが、データベース表で1行に含まれているNAME
、AGE
およびSALARY
のデータを保持しています)を含む構造体の定義を組み込むことができます。アプリケーションでは、次に、これらの構造体配列へデータをフェッチします。
構造体配列を使用して、複数行、複数列の操作を実行するには、その操作に関連する各列を、構造体の中の1フィールドと関連付けておく必要があります。この関連付けは、OCIDefineArrayOfStruct()
コールおよびOCIBindArrayOfStruct()
コールの一部であり、データの格納場所を指定します。
構造体配列間で列データを分割すると、それはデータベース内では連続した列データとして格納されなくなります。単一の構造体配列は、いくつかのスカラー配列で構成されているかのようにデータを格納します。このため、バインドまたは定義を行う各フィールドにスキップ・パラメータを指定する必要があります。スキップ・パラメータは、構造体配列の中にある同じフィールドまでスキップするために必要なバイト数です。通常は、1つの構造体のバイト数と等しい値です。
図5-2は、スキップ・パラメータの決定方法を示しています。このケースでは、スキップ・パラメータは、フィールド1
、フィールド2
およびフィールド3
のサイズの合計です。それぞれのフィールドのサイズは、8バイトです。このサイズは、1つの構造体のサイズと同じです。
オペレーティング・システムによっては、スキップ・パラメータをsizeof
(struct
)ではなく、sizeof
(one_array_element
)に設定することが必要な場合もあります。これは、コンパイラによっては、構造体に余分なバイトを挿入する場合があるからです。
ub4
とub1
という2つのフィールドで構成されたC構造体配列のケースを考えてみます。
struct demo { ub4 field1; ub1 field2; }; struct demo demo_array[MAXSIZE];
コンパイラによっては、この配列内の次の構造体を開始するub4
の位置を正しくするために、ub1
の後に3バイトの埋込みを挿入することがあります。このようなコンパイラでは、次の文を使用すると、誤った値が戻される可能性があります。
skip_parameter = sizeof(struct demo);
この文を使用した場合、8バイトの適切なスキップ・パラメータを戻すオペレーティング・システムもありますが、5バイトのskip_parameter
を戻すシステムもあります。そのような場合、スキップ・パラメータの正しい値を得るには次の文を使用します。
skip_parameter = sizeof(demo_array[0]);
構造体配列に関係する操作を行う際には、次の2つのOCIコールを使用する必要があります。
OCIBindArrayOfStruct()
− 入力変数の構造体配列でフィールドをバインドする場合
OCIDefineArrayOfStruct()
− 出力変数の構造体配列を定義する場合
注意: 構造体配列をバインドまたは定義する際は、複数のコールが必要です。 OCIBindByName() またはOCIBindByPos() へのコールは、OCIBindArrayOfStruct() へのコールに先行し、OCIDefineByPos() へのコールはOCIDefineArrayOfStruct() に先行する必要があります。 |
構造体配列を実装することによって、標識変数とリターン・コードの使用が可能になります。フェッチ、挿入または更新された情報の配列に対応して、列レベルの標識変数とリターン・コードのパラレル配列を宣言できます。これらの配列には、独自のスキップ・パラメータを含めることができます。スキップ・パラメータは、OCIBindArrayOfStruct()
またはOCIDefineArrayOfStruct()
コール中に指定します。
プログラム値および標識変数の構造体配列は、様々な方法で設定できます。データベースの3列から、構造体配列の3フィールドへデータをフェッチするアプリケーションを考えてみます。この場合、3フィールドに対応する標識変数の構造体配列を設定できます。それぞれの標識変数は、データベースからフェッチされる列の中の1列に対する列レベルの標識変数です。インジケータ構造体のフィールドと選択リスト項目の数に1対1関係は必要ありません。
単一のバインドまたは定義コールで使用する複数のバッファを指定できます。ラウンドトリップ数が減少し、様々な不連続アドレスに格納されているデータが1つの連続アドレスにコピーされることがないため、パフォーマンスが向上します。これにより、CPU使用時間とメモリー使用量も減少します。
OCIIOV
データ型の定義は、次のとおりです。
typedef struct OCIIOV { void *bfp; /* The pointer to a buffer for the data */ ub4 bfl; /* The size of the buffer */ }OCIIOV;
複数バッファを作成する場合、OCIBindByPos()
およびOCIBindByName()
関数でmodeパラメータに値OCI_IOV
を使用します。mode
にこの値を指定する場合は、パラメータvaluep
でOCIIOV
のアドレスを渡す必要があります。データ型のサイズは、パラメータvaluesz
で渡す必要があります。たとえば、次のようにします。
OCIIOV vecarr[NumBuffers]; ... /* For bind at position 1 with dataype int */ OCIBindByPos(stmthp, bindp, errhp, 1, (void *)&vecarr[0], sizeof(int), ... OCI_IOV); ...
複数バッファを定義する場合、OCIDefineByPos()
関数でmodeパラメータに値OCI_IOV
を使用します。mode
にこの値を指定する場合は、パラメータvaluep
でOCIIOV
のアドレスを渡します。データ型のサイズは、パラメータvaluesz
で渡す必要があります。
次の例に、構造OCIIOV
およびmode
値の使用方法を示します。
/* The following macros mention the maximum length of the data in the * different buffers. */ #define LENGTH_DATE 10 #define LENGTH_EMP_NAME 100 /* These 2 macros represent the number of elements in each bind and define array */ #define NUM_BIND 30 #define NUM_DEFINE 45 /* The bind buffers for inserting dates */ char buf_1[NUM_BIND][LENGTH_DATE], char buf_2[NUM_BIND * 2][LENGTH_DATE], /* The bind buffer for inserting emp name */ char buf_3[NUM_BIND * 3][LENGTH_EMP_NAME], /* The define buffers */ char buf_4[NUM_DEFINE][LENGTH_EMP_NAME]; char buf_5[NUM_DEFINE][LENGTH_EMP_NAME]; /* The size of data value for buffers corresponding to the same column must be the same and that value is passed in the OCIBind/Define calls. buf_4 and buf_5 above have the same data values ie. LENGTH_EMP_NAME though the number of elements are different in the two buffers. */ OCIBind *bndhp1 = (OCIBind *)0; OCIBind *bndhp2 = (OCIBind *)0; OCIDefine *defhp = (OCIDefine *)0; OCIStmt *stmthp = (OCIStmt *)0; OCIError *errhp = (OCIError *)0; OCIIOV bvec[2], dvec[2]; /* Example of how to use indicators and return codes with this feature, showing the allocation when using with define. We allocate memory for indicator; return code, and the length buffer as one chunk of NUM_DEFINE * 2 elements. */ short *indname[NUM_DEFINE*2]; /* indicators */ ub4 *alenname[NUM_DEFINE*2]; /* return lengths */ ub2 *rcodename[NUM_DEFINE*2]; /* return codes */ static text *insertstr = "INSERT INTO EMP (EMP_NAME, JOIN_DATE) VALUES (:1, :2)"; static text *selectstr = "SELECT EMP_NAME FROM EMP"; /* Allocate environment, error handles etc and initialize the environment */ ... /* Prepare the statement with the insert query in order to show the binds */ OCIStmtPrepare (stmthp, errhp, insertstr, (ub4)strlen((char *)insertstr), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); /* Populate buffers with values. The following represents the simplest * way of populating the buffers. However, in an actual scenario * these buffers may have been populated by data received from different * sources. */ /* Store the date into the bind buffers for the date. */ strcpy(buf_1[0], "21-SEP-02"); ... strcpy(buf_1[NUM_BIND - 1], "21-OCT-02"); ... strcpy(buf_2[0], "22-OCT-02"); ... strcpy(buf_2[2*NUM_BIND - 1], "21-DEC-02"); ... memset(bvec[0], 0, sizeof(OCIIOV)); memset(bvec[1], 0, sizeof(OCIIOV)); /* Set up the addresses in the IO Vector structure */ bvec[0].bfp = buf_1[0]; /* Buffer address of the data */ bvec[0].bfl = NUM_BIND*LENGTH_DATE; /* Size of the buffer */ /* And so on for other structures as well. */ bvec[1].bfp = buf_2[0]; /* Buffer address of the data */ bvec[1].bfl = NUM_BIND*2*LENGTH_DATE; /* Size of the buffer */ /* Do the bind for date, using OCIIOV */ OCIBindByPos (stmthp, &bindhp2, errhp, 2, (void *)&bvec[0], sizeof(buf_1[0]), SQLT_STR, (void *)inddate, (ub2 *)alendate, (ub2 *)rcodedate, 0, (ub4 *)0, OCI_IOV); /* Store the employee names into the bind buffers 3 for the names */ strcpy (buf_3[0], "JOHN "); ... strcpy (buf_3[NUM_BIND *3 - 1], "HARRY"); /* Do the bind for employee name */ OCIBindByPos (stmthp, &bindhp1, errhp, 1, buf_3[0], sizeof(buf_3[0]), SQLT_STR, (void *)indemp, (ub2 *)alenemp, (ub2 *)rcodeemp, 0, (ub4 *)0, OCI_DEFAULT); OCIStmtExecute (svchp, stmthp, errhp, NUM_BIND*3, 0, (OCISnapshot *)0, (OCISnapshot *)0, OCI_DEFAULT); ... /* Now the statement to depict defines */ /* Prepare the statement with the select query in order to show the defines */ OCIStmtPrepare(stmthp, errhp, selectstr,(ub4)strlen((char *)selectstr), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); memset(dvec[0], 0, sizeof(OCIIOV); memset(dvec[1], 0, sizeof(OCIIOV)); /* Set up the define vector */ dvec[0].bfp = buf_4[0]; dvec[0].bfl = NUM_DEFINE*LENGTH_EMP_NAME; dvec[1].bfp = buf_5[0]; dvec[1].bfl = NUM_DEFINE*LENGTH_EMP_NAME; /* Pass the buffers for the indicator, length of the data and the return code. We need to note that, the buffer where we receive the data is split into 2 locations, each having NUM_DEFINE number of elements. However the indicator buffer, the actual length buffer, and the return code buffer is one single chunk of NUM_DEFINE * 2 elements. */ OCIDefineByPos (stmthp, &defhp, errhp, 1, (void *)&dvec[0], sizeof(buf_4[0]), SQLT_STR, (void *)indname, (ub2 *)alenname, (ub2 *)rcodename, OCI_IOV); OCIStmtExecute (svchp, stmthp, errhp, NUM_DEFINE*2, 0, (OCISnapshot*)0, (OCISnapshot*)0, OCI_DEFAULT); ...
OCIでは、SQL INSERT
文、UPDATE
文およびDELETE
文でのRETURNING
句の使用をサポートします。この項では、RETURNING
句を使用してDML文を正しく実装するためのルールについて、その概要を説明します。
関連項目:
|
DML文でRETURNING
句を使用することにより、2つのSQL文を1つに結合でき、サーバー・ラウンドトリップを削減できます。これは、追加の句を従来のUPDATE
文、INSERT
文およびDELETE
文に追加することで実行できます。追加の句では、問合せをDML文に効果的に追加します。
OCIでは、OUT
バインド変数として値がアプリケーションに戻されます。次の例では、バインド変数の前にコロン":"が付いています。これらの例では、col1
、col2
およびcol3
という列を含む表table1
を想定しています。
次の文では、新しい値をデータベースに挿入して、影響を受ける行の列値をデータベースから取り出します。これにより、挿入された行を操作できます。
INSERT INTO table1 VALUES (:1, :2, :3) RETURNING col1, col2, col3 INTO :out1, :out2, :out3
次の例は、col1
の値がある範囲内にあるすべての列の値を更新し、変更による影響を受けた行を戻します。
UPDATE table1 SET col1 = col1 + :1, col2 = :2, col3 = :3 WHERE col1 >= :low AND col1 <= :high RETURNING col1, col2, col3 INTO :out1, :out2, :out3
DELETE
文では、col1
の値が特定の範囲内にある行を削除し、それらの行からデータを戻します。
DELETE FROM table1 WHERE col1 >= :low AND col2 <= :high RETURNING col1, col2, col3 INTO :out1, :out2, :out3
UPDATE
文およびDELETE
文は表内の複数行に影響し、DML文は1回のOCIExecute()
コールで複数回実行できるため、どれだけのデータが戻ってくるのかを実行時に判断できない場合があります。そのため、RETURNING
...INTO
プレースホルダに対応する変数は、OCI_DATA_AT_EXEC
モードでバインドする必要があります。アプリケーションでは、ポーリング・メカニズムを使用するのではなく、アプリケーション自体の動的データ処理コールバックを定義する必要があります。
RETURNING句は、LOBを操作するときに特に役立ちます。通常、アプリケーションでは、空のLOBロケータをデータベースに挿入し、再度選択(SELECT
)して操作を行います。RETURNING
句を使用すると、アプリケーションではこれら2つのステップを1つの文に結合できます。
INSERT INTO some_table VALUES (:in_locator) RETURNING lob_column INTO :out_locator
OCIアプリケーションは、RETURNING
句のプレースホルダを純OUT
バインド変数として実装します。ただし、RETURNING
句のすべてのバインドの初期値はIN
であるため、適切に初期化する必要があります。有効な値を設定するために、NULL
インジケータを使用して、そのインジケータを-1に設定できます。
アプリケーションでは、RETURNING
句のバインド変数を操作するとき、次のルールに従う必要があります。
OCIBindByName()
またはOCIBindByPos()
を使用して、OCI_DATA_AT_EXEC
モードでRETURNING
句のプレースホルダをバインドした後で、各プレースホルダに対してOCIBindDynamic()
をコールします。
RETURNING
句のプレースホルダをバインドする場合は、OCIBindDynamic()
コールのocbfp
パラメータとして、有効なOUT
バインド関数を指定します。この関数では、戻されたデータを保持するための記憶域が必要です。
OCIBindDynamic()
コールのicbfp
パラメータによって、コール時にNULL
値を戻すデフォルトの関数を提供する必要があります。
OCIBindDynamic()
のpiecep
パラメータを、OCI_ONE_PIECE
に設定します。
RETURNING
句を使用したDML文では、複製バインドは使用できません。また、DMLセクションのバインド変数と、その文のRETURNING
セクションのバインド変数を重複させることはできません。
注意: OCIでは、 RETURNING 句バインドのコールバック・メカニズムのみがサポートされています。ポーリング・メカニズムはサポートされていません。 |
OCIBindDynamic()
に提供されたOUT
バインド関数には、エラー時に文の結果の一部を受け取る準備が必要です。10回実行されるDML文をアプリケーションで発行し、5回目の実行中にエラーが発生した場合、サーバーは、1回目から4回目までのデータを戻します。コールバック関数は、最初の4回目までのデータを受け取るためにデータをコールします。
RETURNING
句を使用して、データベースに挿入するオブジェクトやデータベース内で更新するオブジェクトへのREF
を戻すこともできます。
UPDATE extaddr e SET e.zip = '12345', e.state ='AZ' WHERE e.state = 'CA' AND e.zip = '95117' RETURNING REF(e), zip INTO :addref, :zip
前述の文では、オブジェクト表内のオブジェクトの属性をいくつか更新し、RETURNING
句でオブジェクトへのREF(およびスカラーZIPコード)を戻します。
OCIアプリケーションでREF
出力変数をバインドするには、次の3つのステップが必要です。
初期バインド情報を、OCIBindByName()
を使用して設定します。
REF
(TDOを含みます)に対する追加のバインド情報は、OCIBindObject()
を使用して設定します。
OCIBindDynamic()
をコールします。
次のコード例では、前述の例で必要なバインドを実行する関数を示します。
sword bind_output(stmthp, bndhp, errhp) OCIStmt *stmthp; OCIBind *bndhp[]; OCIError *errhp; { ub4 i; /* get TDO for BindObject call */ if (OCITypeByName(envhp, errhp, svchp, (CONST text *) 0, (ub4) 0, (CONST text *) "ADDRESS_OBJECT", (ub4) strlen((CONST char *) "ADDRESS_OBJECT"), (CONST text *) 0, (ub4) 0, OCI_DURATION_SESSION, OCI_TYPEGET_HEADER, &addrtdo)) { return OCI_ERROR; } /* initial bind call for both variables */ if (OCIBindByName(stmthp, &bndhp[2], errhp, (text *) ":addref", (sb4) strlen((char *) ":addref"), (void *) 0, (sb4) sizeof(OCIRef *), SQLT_REF, (void *) 0, (ub2 *)0, (ub2 *)0, (ub4) 0, (ub4 *) 0, (ub4) OCI_DATA_AT_EXEC) || OCIBindByName(stmthp, &bndhp[3], errhp, (text *) ":zip", (sb4) strlen((char *) ":zip"), (void *) 0, (sb4) MAXZIPLEN, SQLT_CHR, (void *) 0, (ub2 *)0, (ub2 *)0, (ub4) 0, (ub4 *) 0, (ub4) OCI_DATA_AT_EXEC)) { return OCI_ERROR; } /* object bind for REF variable */ if (OCIBindObject(bndhp[2], errhp, (OCIType *) addrtdo, (void **) &addrref[0], (ub4 *) 0, (void **) 0, (ub4 *) 0)) { return OCI_ERROR; } for (i = 0; i < MAXCOLS; i++) pos[i] = i; /* dynamic binds for both RETURNING variables */ if (OCIBindDynamic(bndhp[2], errhp, (void *) &pos[0], cbf_no_data, (void *) &pos[0], cbf_get_data) || OCIBindDynamic(bndhp[3], errhp, (void *) &pos[1], cbf_no_data, (void *) &pos[1], cbf_get_data)) { return OCI_ERROR; } return OCI_SUCCESS; }
コールバック関数がコールされるとき、バインド・ハンドルのOCI_ATTR_ROWS_RETURNED
属性により、アプリケーションでは、ある特定の反復処理で戻される行数がわかります。最初のコールバックの反復処理中に、そのバインド変数に対して戻されるすべての行に空白を割り当てることができます。同一の反復処理内で後続のコールバックを実行しているときは、割り当てられた空白内の正確なメモリーまでバッファ・ポインタを増分させます。
OCIでは、各反復処理で複数の行を戻す単一行DML操作および配列DML操作に追加の機能性を提供します。この機能を利用するには、バインド・コールのOUTバッファを、OCIStmtExecute()
コールで指定した反復件数以上の値に指定する必要があります。これは、コールバックを介してバインド・バッファに提供される追加機能です。
いずれかの反復処理で複数の行が戻された場合は、アプリケーションにOCI_SUCCESS_WITH_INFO
リターン・コードが戻されます。この場合、DML操作は正常に完了しています。このとき、アプリケーションでは、トランザクションをロールバックするか、警告を無視します。
この項では、クライアントとサーバー間での文字変換に関する問題について説明します。
文字データを含むデータベースの列がNCHAR
またはNVARCHAR2
列として定義されている場合、その列に関連するバインドまたは定義では、キャラクタ・セット仕様の処理について特別な配慮が必要です。
クライアントとサーバーのキャラクタ・セットの幅が異なる場合、および適切に文字変換を行うために、これらの考慮が必要です。異なるキャラクタ・セット間でデータを変換する間に、データ・サイズが、4倍に増加したり4分の1に縮小する場合があります。そのため、データを保存するためのバッファのサイズを十分に確保してください。
アプリケーションでは、NCHAR
またはNVARCHAR2
データをバイト数(通常の場合)ではなく文字数で処理する方が簡単な場合があります。
各OCIバインドおよび定義ハンドルには、OCI_ATTR_CHARSET_FORM
属性およびOCI_ATTR_CHARSET_ID
属性が関連付けられています。アプリケーションでは、バインド/定義バッファのキャラクタ・フォームおよびキャラクタ・セットIDを指定するために、OCIAttrSet()
コールを使用してこれらの属性を設定できます。
csform
属性(OCI_ATTR_CHARSET_FORM
)は、バインドの場合はクライアント・バッファのキャラクタ・セットを示し、定義の場合はフェッチされたデータを格納するキャラクタ・セットを示します。この属性の値は、次のうちいずれかになります。
SQLCS_IMPLICIT − デフォルト値です。この値はバインド・バッファまたは定義バッファ用のデータベース・キャラクタ・セットIDを示し、文字バッファ・データはサーバーのデータベース・キャラクタ・セットに変換されます。
SQLCS_NCHAR − バインド・バッファまたは定義バッファ用の各国語キャラクタ・セットIDを示し、クライアントのバッファ・データはサーバーの各国語キャラクタ・セットに変換されます。
キャラクタ・セットID属性OCI_ATTR_CHARSET_ID
が指定されていない場合は、csform
の値に応じて、クライアントのデータベース・キャラクタ・セットIDまたは各国語キャラクタ・セットIDのデフォルト値が使用されます。これらの値はそれぞれ、環境変数NLS_LANGまたはNLS_NCHARで指定された値です。
注意:
|
キャラクタ・セットは、OCIEnvNlsCreate()
関数のcharset
パラメータおよびncharset
パラメータを使用して設定できます。両方のパラメータともOCI_UTF16ID
を設定できます。charset
パラメータはメタデータおよびCHAR
データのコーディングを制御し、ncharset
パラメータはNCHAR
データのコーディングを制御します。OCINlsEnvironmentVariableGet()
関数は、NLS_LANGのキャラクタ・セットおよびNLS_NCHARの各国語キャラクタ・セットを戻します。
次のコードは、これらの関数使用例です(OCIには、UTF-16データのバインドと定義を容易にするために、utext
と呼ばれるtypedefが用意されています。
OCIEnv *envhp; ub2 ncsid = 2; /* we8dec */ ub2 hdlcsid, hdlncsid; OraText thename[20]; utext *selstmt = L"SELECT ename FROM emp"; /* UTF16 statement */ OCIStmt *stmthp; OCIDefine *defhp; OCIError *errhp; OCIEnvNlsCreate(OCIEnv **envhp, ..., OCI_UTF16ID, ncsid); ... OCIStmtPrepare(stmthp, ..., selstmt, ...); /* prepare UTF16 statement */ OCIDefineByPos(stmthp, defnp, ..., 1, thename, sizeof(thename), SQLT_CHR,...); OCINlsEnvironmentVariableGet(&hdlcsid, (size_t)0, OCI_NLS_CHARSET_ID, (ub2)0, (size_t*)NULL); OCIAttrSet(defnp, ..., &hdlcsid, 0, OCI_ATTR_CHARSET_ID, errhp); /* change charset ID to NLS_LANG setting*/ ...
更新操作または挿入操作は、変数のバインドによって行われます。変数をバインドするときは、バインド・ハンドルのOCI_ATTR_MAXCHAR_SIZE
およびOCI_ATTR_MAXDATA_SIZE
を指定して、サーバーにデータを挿入するときに使用する文字制約とバイト制約を指定します。
これらの属性は、次のように定義します。
すべてのバインド・ハンドルにはOCI_ATTR_MAXDATA_SIZE
属性があり、キャラクタ・セットの変換後、クライアント側バインド・データを格納するためにサーバーに割り当てるバイト数が指定されます。
通常、アプリケーションでは、使用方法に応じて、OCI_ATTR_MAXDATA_SIZE
に、列の最大サイズまたはPL/SQL変数のサイズを設定します。OCI_ATTR_MAXDATA_SIZE
が変換後のデータを格納するのに十分でない場合、エラーが発生します。
IN/INOUT
バインドの場合、OCI_ATTR_MAXDATA_SIZE属性を設定する場合、バインド・バッファは、キャラクタ・セットの各文字のバイト数を乗算した文字数を保持できる大きさである必要があります。
OCI_ATTR_MAXCHAR_SIZE
が100などの0(ゼロ)以外の値に設定されている場合で、キャラクタ・セットの各文字が2バイトである場合は、割当て可能な最小サイズは200バイトになります。
次のシナリオでは、OCI_ATTR_MAXDATA_SIZE
属性の使用例を示します。
シナリオ1: CHAR(ソース・データ →非CHAR(宛先列)
データに対して暗黙的バインド変換が行われます。ソース・バッファ・サイズに、クライアント側とサーバー側間で行われるキャラクタ・セットの変換の最大値を加算した値を、OCI_ATTR_MAXDATA_SIZE
の値として設定することをお薦めします。
シナリオ2: CHAR(ソース・データ)→CHAR(宛先列)、または非CHAR(ソース・データ)→CHAR(宛先列)
列のサイズを、OCI_ATTR_MAXDATA_SIZE
の値として設定することをお薦めします。
シナリオ3: CHAR(ソース・データ)→PL/SQL変数
このケースでは、PL/SQL変数のサイズを、OCI_ATTR_MAXDATA_SIZE
の値として設定することをお薦めします。
OCI_ATTR_MAXCHAR_SIZE
を使用すると、バイト数ではなく文字数でデータを処理できます。
バインドについては、OCI_ATTR_MAXCHAR_SIZE
属性を使用して、サーバーに確保する文字数を設定し、バインド・データを格納します。
たとえば、OCI_ATTR_MAXDATA_SIZEが100に設定され、OCI_ATTR_MAXCHAR_SIZE
が0(ゼロ)に設定されている場合、サーバーでのデータの変換後に可能な最大サイズは100バイトです。ただし、OCI_ATTR_MAXDATA_SIZE
が300に設定され、OCI_ATTR_MAXCHAR_SIZE
が100などの0(ゼロ)以外の値に設定されている場合で、キャラクタ・セットに2バイトまたは2文字がある場合は、割当て可能な最大サイズは200バイトになります。
定義については、クライアント・アプリケーションのバッファに戻すことができる最大文字数をOCI_ATTR_MAXCHAR_SIZE
属性で指定します。導出されたバイト長は、OCIDefineByPos()
コールで指定されたmaxlength
パラメータを上書きします。
注意: バインド・コールまたは定義コールで指定されたバッファ長は、 OCI_ATTR_MAXCHAR_SIZE 属性の値に関係なく、常にバイト数による長さです。また、送受信する実際の長さもバイト数になります。 |
OUTバインドまたはPL/SQLバインドの場合は、OCI_ATTR_MAXDATA_SIZE
を設定しないでください。OCI_ATTR_MAXDATA_SIZE
は、INSERT文またはUPDATE文の場合のみ設定してください。
いずれの属性も設定されていない場合、OCIでは最適な見積りを行ってバッファを拡張します。
基礎となる列が文字長セマンティクスを使用して作成されている場合は、OCI_ATTR_MAXCHAR_SIZE
を使用して制約を指定することをお薦めします。実際のバッファに含まれる文字数がOCI_ATTR_MAXCHAR_SIZE
で指定した文字数より少なければ、OCIレベルでの制約違反は発生しません。
基礎となる列がバイト長セマンティクスを使用して作成されている場合は、バインド・ハンドルのOCI_ATTR_MAXDATA_SIZE
を使用して、サーバーでのバイト制約を指定します。OCI_ATTR_MAXCHAR_SIZE
の値も指定している場合は、この制約がサーバー側の受信バッファの割当て時に適用されます。
動的SQLの場合は、パラメータ・ハンドルのOCI_ATTR_DATA_SIZE
およびOCI_ATTR_CHAR_SIZE
を取得するための明示的な記述を、バインド・ハンドルのOCI_ATTR_MAXDATA_SIZE
属性およびOCI_ATTR_MAXCHAR_SIZE
属性を設定するためのガイドとして使用できます。OCI_ATTR_MAXDATA_SIZE
およびOCI_ATTR_MAXCHAR_SIZE
を、実際の列幅より少ない値(バイト数または文字数)に設定することをお薦めします。
挿入時のバッファ拡張による予期しない動作の発生を回避する必要があります。
データベース列に文字長セマンティクスがあり、その列にユーザーがOCIBindByPos()
またはOCIBindByName()
を使用してデータの挿入を試み、OCI_ATTR_MAXCHAR_SIZE
のみを3000バイトに設定したとします。データベース・キャラクタ・セットはUTF8で、クライアント・キャラクタ・セットはASCIIです。この場合、クライアント側ではサイズが3000バイトのバッファに3000文字が入りますが、サーバー側ではバッファが拡張されて4000バイトを超えることになります。基礎となる列がLONG型またはLOB型でないかぎり、サーバーでエラーが発生します。この問題を回避するには、OCI_ATTR_MAXDATA_SIZE
を4000バイトに設定します。これにより、データが4000バイトを超えることはありません。
OCIでは、列からクライアント・バッファへのデータ選択に事前定義の変数が使用されます。OCI_ATTR_MAXCHAR_SIZE
値を設定して、その定義バッファに対して文字長の追加の制約を適用できます。バイト単位のバッファ・サイズはバイト長の制限として機能するため、定義ハンドルに対するOCI_ATTR_MAXDATA_SIZE
属性はありません。OCIDefineByPos()
コールで指定された定義バッファのサイズをバイト制約として使用できます。
動的SQLのバッファ・サイズを指定するときは、切捨てによるデータの損失を回避するために、常に暗黙的な記述でOCI_ATTR_DATA_SIZE
値を使用します。データベース列が文字長セマンティクスを使用して作成されている場合(OCI_ATTR_CHAR_USED
属性によって判明します)は、OCI_ATTR_MAXCHAR_SIZE
値を使用して、定義バッファに追加の制約を設定できます。OCI_ATTR_MAXCHAR_SIZE
値の最大文字数がバッファに配置されます。
次の長さの値は、データベースの文字長セマンティクスに関係なく、常にバイト単位で戻されます。
alen
で戻される値、つまり、バインドと定義での実際の長さフィールド
VARCHAR
やLONG
VARCHAR
など、特殊なデータ型の前に付けられる長さの値
切捨てが発生した場合のための標識変数の値
この規則の唯一の例外は、OCI_UTF16ID
キャラクタ・セットIDの文字列バッファです。この文字列バッファでは、長さの値はUTF-16単位になります。
注意: バインド・コールと定義コールのバッファ・サイズ、 OCIGetPieceInfo() とOCISetPieceInfo() のピース・サイズおよびコールバックは常にバイト単位になります。 |
リリース8.1以下のサーバーと通信を行うリリース1(9.0.1)以上のクライアントの場合、OCI_ATTR_MAXCHAR_SIZE
はサーバーで認識されないため、値は無視されます。この値のみを指定した場合、OCIでは、クライアント側キャラクタ・セットの各文字の最大バイト数に基づいて、対応するOCI_ATTR_MAXDATA_SIZE
値が導出されます。
リリース1(9.0.1)以上のサーバーと通信を行うリリース8.1以下のクライアントの場合は、クライアントでOCI_ATTR_MAXCHAR_SIZE
値を指定できないため、サーバーでは、クライアントが常にバイト長セマンティクスを使用しているものとみなします。これは、クライアントでOCI_ATTR_MAXDATA_SIZE
のみを指定した場合に類似しています。
いずれの場合も、サーバーとクライアントは適切な方法で情報を交換できます。
文字数N
を指定して列が作成されると、次の表では、データベースでの実際の割当てが最大値で想定されます。割り当てられる実際のバイト数は、N
の倍数、つまりN
のM
倍です。現在、UTF8では各文字の最大バイト数として、M
が3に設定されています。
たとえば、次の表のEMP
では、ENAME
列が30文字に定義され、ADDRESS
列は80文字に定義されています。これに対応するデータベースでのバイト長はそれぞれ、M×30または3×30 = 90、およびM×80または3×80 = 240になります。
... utext ename[31], address[81]; /* E' <= 30+ 1, D' <= 80+ 1, considering null-termination */ sb4 ename_max_chars = EC=20, address_max_chars = ED=60; /* EC <= (E' - 1), ED <= (D' - 1) */ sb4 ename_max_bytes = EB=80, address_max_bytes = DB=200; /* EB <= M * EC, DB <= M * DC */ text *insstmt = (text *)"INSERT INTO EMP(ENAME, ADDRESS) VALUES (:ENAME, \ :ADDRESS)"; text *selstmt = (text *)"SELECT ENAME, ADDRESS FROM EMP"; ... /* Inserting Column Data */ OCIStmtPrepare(stmthp1, errhp, insstmt, (ub4)strlen((char *)insstmt), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIBindByName(stmthp1, &bnd1p, errhp, (text *)":ENAME", (sb4)strlen((char *)":ENAME"), (void *)ename, sizeof(ename), SQLT_STR, (void *)&insname_ind, (ub2 *)alenp, (ub2 *)rcodep, (ub4)maxarr_len, (ub4 *)curelep, OCI_DEFAULT); /* either */ OCIAttrSet((void *)bnd1p, (ub4)OCI_HTYPE_BIND, (void *)&ename_max_bytes, (ub4)0, (ub4)OCI_ATTR_MAXDATA_SIZE, errhp); /* or */ OCIAttrSet((void *)bnd1p, (ub4)OCI_HTYPE_BIND, (void *)&ename_max_chars, (ub4)0, (ub4)OCI_ATTR_MAXCHAR_SIZE, errhp); ... /* Retrieving Column Data */ OCIStmtPrepare(stmthp2, errhp, selstmt, strlen((char *)selstmt), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIDefineByPos(stmthp2, &dfn1p, errhp, (ub4)1, (void *)ename, (sb4)sizeof (ename), SQLT_STR, (void *)&selname_ind, (ub2 *)alenp, (ub2 *)rcodep, (ub4)OCI_DEFAULT); /* if not called, byte semantics is by default */ OCIAttrSet((void *)dfn1p, (ub4)OCI_HTYPE_DEFINE, (void *)&ename_max_chars, (ub4)0, (ub4)OCI_ATTR_MAXCHAR_SIZE, errhp); ...
CHAR
/VARCHAR2
改良型ハンドルまたはNCHAR
/NVARCHAR
改良型ハンドルのバインドと定義におけるキャラクタ・セットIDは、渡されるすべてのデータがUTF-16(Unicode)エンコーディングであるとみなすように設定できます。UTF-16を指定するには、OCI_ATTR_CHARSET_ID
= OCI_UTF16ID
を設定します。
OCIには、UTF-16データのバインドと定義を容易にするため、utext
と呼ばれるtypedefが用意されています。utext
の内部表現は、16ビットの符号なし整数(ub2
)です。wchar_t
データ型のコード体系がUTF-16に準拠しているオペレーティング・システムでは、キャスト演算子を使用してutext
をwchar_t
データ型に容易に変換できます。
UTF-16データの場合でも、バインド・コールと定義コールでのバッファ・サイズはバイト単位とみなされます。utext
データ型は、入出力データのバッファとして使用してください。
次の擬似コードは、UTF-16データのバインドと定義の例です。
... OCIStmt *stmthp1, *stmthp2; OCIDefine *dfn1p, *dfn2p; OCIBind *bnd1p, *bnd2p; text *insstmt= (text *) "INSERT INTO EMP(ENAME, ADDRESS) VALUES (:ename, :address)"; \ text *selname = (text *) "SELECT ENAME, ADDRESS FROM EMP"; utext ename[21]; /* Name - UTF-16 */ utext address[51]; /* Address - UTF-16 */ ub2 csid = OCI_UTF16ID; sb4 ename_col_len = 20; sb4 address_col_len = 50; ... /* Inserting UTF-16 data */ OCIStmtPrepare (stmthp1, errhp, insstmt, (ub4)strlen ((char *)insstmt), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIBindByName (stmthp1, &bnd1p, errhp, (text*)":ENAME", (sb4)strlen((char *)":ENAME"), (void *) ename, sizeof(ename), SQLT_STR, (void *)&insname_ind, (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *)0, OCI_DEFAULT); OCIAttrSet ((void *) bnd1p, (ub4) OCI_HTYPE_BIND, (void *) &csid, (ub4) 0, (ub4)OCI_ATTR_CHARSET_ID, errhp); OCIAttrSet((void *) bnd1p, (ub4) OCI_HTYPE_BIND, (void *) &ename_col_len, (ub4) 0, (ub4)OCI_ATTR_MAXDATA_SIZE, errhp); ... /* Retrieving UTF-16 data */ OCIStmtPrepare (stmthp2, errhp, selname, strlen((char *) selname), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIDefineByPos (stmthp2, &dfn1p, errhp, (ub4)1, (void *)ename, (sb4)sizeof(ename), SQLT_STR, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)OCI_DEFAULT); OCIAttrSet ((void *) dfn1p, (ub4) OCI_HTYPE_DEFINE, (void *) &csid, (ub4) 0, (ub4)OCI_ATTR_CHARSET_ID, errhp); ...
OCIには、PL/SQL REF
CURSOR
およびNESTED TABLEを、バインドおよび定義する機能があります。アプリケーションでは、これらの型の変数をハンドルおよび定義するために、文ハンドルを使用できます。次の例で、このPL/SQLブロックを考えてみます。
static const text *plsql_block = (text *) "begin \ OPEN :cursor1 FOR SELECT employee_id, last_name, job_id, manager_id, \ salary, department_id \ FROM employees WHERE job_id=:job ORDER BY employee_id; \ OPEN :cursor2 FOR SELECT * FROM departments ORDER BY department_id; end;";
アプリケーションは、OCIHandleAlloc()
をコールして、バインドのための文ハンドルを割り当てます。そして、次のコード例の中で、:cursor1
がstm2p
にバインドされるように、プレースホルダ:cursor1
を文ハンドルにバインドします。
status = OCIStmtPrepare (stm1p, errhp, (text *) plsql_block, strlen((char *)plsql_block), OCI_NTV_SYNTAX, OCI_DEFAULT); ... status = OCIBindByName (stm1p, (OCIBind **) &bnd1p, errhp, (text *)":cursor1", (sb4)strlen((char *)":cursor1"), (void *)&stm2p, (sb4) 0, SQLT_RSET, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, (ub4)OCI_DEFAULT);
このコードでは、stm1p
はPL/SQLブロックの文ハンドルです。一方、stm2p
は、後のデータ検索のためのREF
CURSOR
としてバインドされる文ハンドルです。SQLT_RSETの値は、dty
パラメータに渡されます。
別の例として、次のコードを考えてみます。
static const text *nst_tab = (text *) "SELECT last_name, CURSOR(SELECT department_name, location_id \ FROM departments) FROM employees WHERE last_name = 'FORD'";
2番目の位置がNESTED TABLEで、次のように、OCIアプリケーションで文ハンドルとして定義できます。
status = OCIStmtPrepare (stm1p, errhp, (text *) nst_tab, strlen((char *)nst_tab), OCI_NTV_SYNTAX, OCI_DEFAULT); ... status = OCIDefineByPos (stm1p, (OCIDefine **) &dfn2p, errhp, (ub4)2, (void *)&stm2p, (sb4)0, SQLT_RSET, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)OCI_DEFAULT);
実行後、行をstm2p
にフェッチすると、有効な文ハンドルになります。
注意: 複数の REF CURSOR を取り出した場合は、stm2p へのフェッチをいつ行うか注意する必要があります。最初のREF CURSORをフェッチすると、そのデータを取り出すためにフェッチを実行できます。ただし、2番目のREF CURSOR をstm2p にフェッチすると、最初のREF CURSOR からのデータにはアクセスできなくなります。
OCIでは、スクロール・モードで実行されたPL/SQL OCIでは、 |
OCIを使用すると、データをピース単位で挿入、更新およびフェッチできます。 また、配列の挿入または更新の場合は、バインド値の静的配列のかわりに、OCIを使用して動的にデータを提供できます。非常に大きな列は、小さなサイズのチャンク(かたまり)が連続したものとして挿入または取出しができます。これによって、クライアント側のメモリー所要量を最小限にできます。
個々のピースのサイズは、実行時にアプリケーションにより判断され、統一することもできます。
OCIのピース単位機能は、文字列やバイナリ・データの膨大なブロックで操作(CLOB
、BLOB
、LONG
、RAW
、LONG
RAW
などのデータを格納するデータベース列に関係する操作など)を行う際に特に有効です。
最後のOCIStmtFetch()
コールがOCI_SUCCESSの値を戻すと、ピース単位フェッチが完了します。
ピース単位のフェッチおよび挿入では、操作を正常に完了するために必要なコールの順序を理解することが重要です。ピース単位挿入では、コールバックが使用されない場合、挿入するピースの数よりも1回多くOCIStmtExecute()
をコールする必要があります。これは、最初のOCIStmtExecute()
コールが、単に、最初のピースの挿入が必要であることを示す値を戻すのみであるからです。結果として、n個のピースを挿入する場合は、OCIStmtExecute()
を合計n+1回コールする必要があります。
同様に、ピース単位フェッチを実行するときは、フェッチするピースの数より1回多くOCIStmtFetch()
をコールする必要があります。
PL/SQL索引付き表をバインドする場合は、OCIStmtGetPieceInfo()
コール中に、その表のカレント索引へのポインタを取り出すことができます。
一部のデータ型のみが、ピース単位で操作できます。次のデータ型は、OCIアプリケーションでピース単位のフェッチ、挿入または更新を実行できます。
VARCHAR2
STRING
LONG
LONG
RAW
RAW
CLOB
BLOB
すべてのデータ型に対してこの機能を使用する別な方法として、配列の挿入または更新のためにデータを動的に提供する方法があります。ピース単位操作をサポートしないデータ型に対しては、コールバックで、そのコールバックのpiecep
パラメータのOCI_ONE_PIECE
を常に指定する必要があります。
ピース単位操作は、次の2通りの方法で実行できます。
ポーリング・パラダイムに基づいてピース単位操作を実行するには、OCIライブラリで提供されているコールを使用します。
必要な情報およびデータ・ブロックを提供するには、ユーザー定義のコールバック関数を使用します。
OCIBindByPos()
コールまたはOCIBindByName()
コールのmode
パラメータをOCI_DATA_AT_EXEC
に設定すると、OCIアプリケーションでは実行時にINSERT
またはUPDATE
のためのデータが動的に提供されます。
同様に、OCIDefineByPos()
コールのmode
パラメータをOCI_DYNAMIC_FETCH
に設定した場合は、アプリケーションが、フェッチ時にデータを受け取るための割当てスペースを動的に提供することを示します。
どちらの場合でも、コールバック関数またはピース単位操作のいずれかの方法によって、INSERT
、UPDATE
またはFETCH
のランタイム情報を提供できます。コールバックを使用するときはコールバックを登録するために、追加のバインド・コールまたは定義コールが必要です。
次項以降では、挿入、更新およびフェッチのためのデータ割当てとピース単位操作に関する特有の情報について説明します。
注意: ピース単位操作は、SQLブロックおよびPL/SQLブロックでも有効です。 |
OCIBindByPos()
コールまたはOCIBindByName()
コールでOCI_DATA_AT_EXEC
を指定すると、value_sz
パラメータによって、実行時に提供できるデータの合計サイズが定義されます。アプリケーションでは、実行時IN
データ・バッファを、要求に応じて操作完了に必要な回数のみOCIライブラリに提供する用意をしておく必要があります。割り当てたバッファは、不要になった際にクライアント側で解放する必要があります。
ランタイム・データは、次の2つの方法のどちらかを使用して提供されます。
OCIBindDynamic()
関数を使用したコールバックを定義できます。このコールバックは実行時にコールすると、データのピースまたはデータ全体を戻します。
コールバックを1つも定義していない場合は、SQL文を処理するOCIStmtExecute()
コールは、OCI_NEED_DATA
エラー・コードを戻します。次に、クライアント・アプリケーションは、使用されるバインドおよびピースを指定するOCIStmtSetPieceInfo()
コールを使用して、IN/OUT
データ・バッファまたはピースを提供します。
OCI環境が初期化され、データベース接続およびセッションが確立すると、SQL文またはPL/SQL文を準備するコールと入力値をバインドするコールが実行され、ピース単位挿入が開始されます。ユーザー定義のコールバックではなく、標準のOCIコールを使用するピース単位操作では、OCIBindDynamic()
をコールする必要はありません。
注意: ピース単位操作の一部でない追加のバインド変数は、データ型に応じて、追加のバインド・コールが必要な場合があります。 |
文の準備およびバインドの次に、アプリケーションでOCIStmtExecute()
、OCIStmtGetPieceInfo()
およびOCIStmtSetPieceInfo()
を連続してコールし、ピース単位操作を完了します。それぞれのOCIStmtExecute()
コールは、次に実行する処理を決定する値を戻します。一般的には、アプリケーションで次のピースの挿入を指示する値を取り出し、そのピースをバッファに移してから挿入を行います。最後のピースを挿入すると操作は完了します。
挿入バッファは任意のサイズにでき、実行時に提供されることに注意してください。また、挿入する各ピースは、同じサイズである必要はありません。挿入するピースのサイズは、それぞれOCIStmtSetPieceInfo()
コールで設定します。
注意: すべての挿入で同じサイズのピースが使用され、挿入されるデータのサイズがそのピースのサイズで均等に割り切れない場合は、挿入される最後のピースは小さくなります。このことを考慮して、最後の OCIStmtSetPieceInfo() コールで小さいサイズを指定する必要があります。 |
図5-3にこの手順を示しています。
OCI環境を初期化して必要なハンドルを割り当てます。サーバーに接続してユーザーを認証し、文の要求を準備します。
OCIBindByName()
またはOCIBindByPos()
を使用して、プレースホルダをバインドします。使用するピースの実際のサイズを指定する必要はありません。実行時に提供できるデータの全体のサイズを指定します。
OCIStmtExecute()
の最初のコールを実行します。この時点ではデータは挿入されていません。アプリケーションにOCI_NEED_DATA
エラー・コードが戻されます。その他の値が戻された場合は、エラーが起きたことを示しています。
OCIStmtGetPieceInfo()
をコールし、挿入する必要があるピースの情報を取り出します。OCIStmtGetPieceInfo()
のパラメータには、必要なピースが最初のピース(OCI_FIRST_PIECE
)なのか、後続のピース(OCI_NEXT_PIECE
)なのかを示す値を戻すポインタが含まれています。
アプリケーションは、挿入するデータのピースをバッファに移し、次のパラメータを使用してOCIStmtSetPieceInfo()
をコールします。
ピースへのポインタ
ピースの長さへのポインタ
次のピースかどうかを示す値
最初のピース(OCI_FIRST_PIECE
)
中間のピース(OCI_NEXT_PIECE
)
最後のピース(OCI_LAST_PIECE
)
再度、OCIStmtExecute()
をコールします。ステップ5でOCI_LAST_PIECE
が示され、OCIStmtExecute()
がOCI_SUCCESS
を戻した場合は、すべてのピースが正常に挿入されています。OCIStmtExecute()
でOCI_NEED_DATA
が戻された場合は、ステップ3に戻り、次の挿入を行います。OCIStmtExecute()
がこれ以外の値を戻した場合は、エラーが発生しています。
最後のピースが正常に挿入されると、ピース単位操作は完了します。この完了は、最後のOCIStmtExecute()
コールによるOCI_SUCCESS
戻り値によって示されます。
ピース単位更新も同様の方法で実行します。ピース単位更新操作では、更新対象のデータが挿入バッファに移され、OCIStmtExecute()
がコールされて更新が実行されます。
OCIアプリケーションでは、前に概説した方法と似た方法で、IN
、OUT
およびIN/OUT
バインド変数について、PL/SQLでのピース単位操作を実行できます。PL/SQL文のすべてのプレースホルダは、定義ではなくバインドされることに注意してください。OCIBindDynamic()
のコールでは、OUT
パラメータまたはIN/OUT
パラメータについて、適切なコールバックを指定します。
mode
パラメータにOCI_DYNAMIC_FETCH
を設定してOCIDefineByPos()
をコールすると、アプリケーションでは、フェッチ時のデータ・バッファに関する情報を指定できます。また、OCIDefineDynamic()
をコールして、データ・バッファに関する情報を取得するために呼び出すコールバック関数を設定する必要がある場合があります。
ランタイム・データは、次の2つの方法のいずれかで提供されます。
OCIDefineDynamic()
を使用したコールバックを定義できます。実行時に提供されるデータの最大サイズは、value_sz
パラメータによって定義されます。クライアント・ライブラリで、フェッチ済データを戻すためのバッファが必要な場合は、このコールバックが呼び出され、ピースまたは全体のデータを戻すための実行時バッファが提供されます。
コールバックが1つも定義されていない場合は、OCI_NEED_DATA
エラー・コードが戻り、クライアント・アプリケーションでOCIStmtSetPieceInfo()
を使用して、OUT
データ・バッファまたはピースを提供します。OCIStmtGetPieceInfo()
コールにより、どの定義およびピースが含まれるかについての情報が提供されます。
フェッチ・バッファは、任意のサイズにすることができます。また、フェッチする各ピースは、同じサイズである必要はありません。ただし、最後にフェッチするサイズは、残っている最後のピースに一致する必要があります。フェッチする各ピースのサイズは、OCIStmtSetPieceInfo()
コールでそれぞれ設定します。図5-4にこの手順を示しています。
OCI環境を初期化して必要なハンドルを割り当てます。データベースに接続してユーザーを認証します。文を準備して実行します。
mode
をOCI_DYNAMIC_FETCH
に設定し、OCIDefineByPos()
を使用して出力変数を定義します。この時点では、使用するピースの実際のサイズを指定する必要はありません。実行時にフェッチするデータの全体のサイズを指定します。
OCIStmtFetch()
の最初のコールを実行します。データは取り出されていません。アプリケーションにOCI_NEED_DATA
エラー・コードが戻されます。その他の値が戻された場合は、エラーが起きています。
OCIStmtGetPieceInfo()
をコールし、フェッチするピースに関する情報を取得します。piecep
パラメータは、そのピースが最初のピース(OCI_FIRST_PIECE
)であるか、その後続のピース(OCI_NEXT_PIECE
)であるか、または最後のピース(OCI_LAST_PIECE
)であるかを示します。
OCIStmtSetPieceInfo()
をコールして、フェッチ・バッファを指定します。
再度、 OCIStmtFetch()
をコールし、実際のピースを取り出します。OCIStmtFetch()
でOCI_SUCCESS
が戻された場合は、すべてのピースが正常にフェッチされています。OCIStmtFetch()
でOCI_NEED_DATA
が戻された場合は、ステップ4に戻って次のピースを処理します。その他の値が戻された場合は、エラーが起きています。
LOBのピース単位のバインドおよび定義を実行するには、次の2つの方法があります。
データ・インタフェースの使用
次の関数の入力データ型として、SQLT_CHR
(VARCHAR2
)またはSQLT_LNG
(LONG
)を使用して、CLOB
列の文字データをバインドまたは定義できます。次の関数の入力データ型として、SQLT_LBI
(LONG
RAW
)およびSQLT_BIN
(RAW
)を使用して、BLOB
列の行データをバインドまたは定義することもできます。
OCIDefineByPos()
OCIBindByName()
OCIBindByPos()
関連項目:
|
次に説明するピース単位操作はすべて、この場合、CLOB
列およびBLOB
列に対してサポートされています。
LOBロケータの使用
次の関数の入力データ型として、SQLT_CLOB
(CLOB
)またはSQLT_BLOB
(BLOB
)を使用して、CLOB
列およびBLOB
列のLOBロケータをバインドまたは定義できます。
OCIDefineByPos()
OCIBindByName()
OCIBindByPos()
次に、OCILob*関数をコールし、データを読み取って操作する必要があります。OCILobRead2()
およびOCILobWrite2()
は、ピース単位モードおよびコールバック・モードをサポートしています。