7 OCIでのバインドおよび定義

この章では、OCIでのバインドと定義について説明します。

この章には次のトピックが含まれます:

OCIでのバインドの概要

この章では、バインドと定義の基本概念をもう一度取り上げ、OCIアプリケーションで使用できる様々な種類のバインドと定義を詳しく説明します。

さらに、構造体配列の使用方法、およびバインド、定義、文字変換における諸問題についても説明します。

たとえば、次のようなINSERT文があるとします。

INSERT INTO emp VALUES
    (:empno, :ename, :job, :sal, :deptno)

次の変数宣言を行います。

text     *ename, *job;
sword    empno, sal, deptno;

バインド・ステップでは、プレースホルダ名とプログラム変数のアドレス間の関連付けを行います。また、図7-1に示すように、バインドでは、プログラム変数のデータ型と長さを指定します。

図7-1 OCIBindByName()を使用したプレースホルダとプログラム変数の関連付け

図7-1の説明が続きます
「図7-1 OCIBindByName()を使用したプレースホルダとプログラム変数の関連付け」の説明

バインド変数の値のみを変更した場合は、文を再実行するために再バインドする必要はありません。バインドは参照によるため、変数のアドレスおよびハンドルが有効であるかぎり、再バインドせずに、変数を参照する文を再実行できます。

ノート:

インタフェース・レベルの場合は、バインド変数はすべてINであるとみなされるため、適切に初期化する必要があります。変数が純OUTバインド変数の場合は、この変数を0 (ゼロ)に設定できます。また、NULLインジケータを使用して、そのインジケータを-1 (NULL)に設定することもできます。

Oracle Databaseでは、名前付きデータ型、REFおよびLOB用のデータ型が実装されおり、これらは、SQL文中でプレースホルダとしてバインドできます。

ノート:

記述子やロケータなど、サイズが不明で不透明なデータ型の場合は、記述子またはロケータ・ポインタのアドレスを渡してください。サイズ・パラメータに、適切なデータ構造のサイズ(sizeof(structure))を設定します。

関連項目:

この例が実装されているコードは、「OCIバインドで使用するステップ」を参照してください

名前付きバインドおよび定位置バインド

名前付きバインドでは、文の各プレースホルダに名前が関連付けられていますが、定位置バインドでは、プレースホルダは名前ではなく文中の位置によって参照されます。

図7-1のSQL文は、名前付きバインドの例です。文の各プレースホルダには、'ename'や'sal'などの名前が関連付けられています。この文を準備し、プレースホルダをアプリケーションで値に関連付けるときは、OCIBindByName()コールまたはOCIBindByName2()コールを使用し、placeholderパラメータでプレースホルダの名前を渡して、プレースホルダの名前による関連付けを行います。

2番目のタイプのバインドは、位置指定バインドと呼ばれます。定位置バインドでは、プレースホルダは名前ではなく文中の位置によって参照されます。このバインドでは、OCIBindByPos()コールまたはOCIBindByPos2()コールを使用して、入力値とプレースホルダの位置との間の関連付けを行います。

前の項に示した例を定位置バインドに使用するには:

INSERT INTO emp VALUES
    (:empno, :ename, :job, :sal, :deptno)

5つのプレースホルダは、OCIBindByPos()またはOCIBindByPos2()をコールし、positionパラメータでプレースホルダの位置番号を渡すことによって、それぞれバインドされます。たとえば、OCIBindByPos()またはOCIBindByPos2()をコールすることによって、:empnoプレースホルダは位置1に、:enameは位置2にというようにバインドされます。

重複バインドでは、1つのバインド・コールのみが必要です。次のSQL文について考えてみます。このSQL文は、コミッションと給料の両方が指定の金額よりも高い従業員をデータベースで問い合せます。

SELECT empno FROM emp
    WHERE sal > :some_value
    AND comm > :some_value

OCIアプリケーションでは、OCIBindByName()またはOCIBindByName2()を1回コールするのみで、:some_valueプレースホルダを名前によってバインドし、この文のバインドを完了できます。この場合、:some_valueのすべてのバインド・プレースホルダには、OCIBindByName()またはOCIBindByName2()コールで指定したものと同じ値が割り当てられます。

次に、重複する6つ目のプレースホルダが追加される場合を考えてみます。たとえば、:enameを6つ目のプレースホルダとして前の最初の例に追加します。

INSERT INTO emp VALUES
    (:empno, :ename, :job, :sal, :deptno, :ename)

OCIBindByName()またはOCIBindByName2()コールを使用している場合、両方の:enameプレースホルダをバインドするには1つのバインド・コールで十分です。文中のすべての:enameは、同じ値にバインドされます。さらに、新しいバインド・プレースホルダが、既存のバインド・プレースホルダのバインド位置が変わった結果として追加される場合、バインド位置を更新するために、既存のバインド・コールを変更する必要はありません。これは、プログラムが文のテキストにさらに多くのバインド変数を追加するようになった場合に、OCIBindByName()またはOCIBindByName2()コールを使用する上で明らかに有利な点です。

ただし、OCIBindByPos()またはOCIBindByPos2()コールを使用していれば、重複するバインド・パラメータのバインドに関しては、それが必要な場合、柔軟性が高まります。バインド・パラメータの重複をどれでも別々にバインドするオプションがあります。バインドを解除された重複パラメータは、同じ名前を持つ最初のバインド・パラメータから値を継承します。最初のものは、明示的にバインドする必要があります。

SQL文のコンテキストでは、位置nは、n番目の位置にあるバインド・パラメータを示します。ただし、PL/SQL文のコンテキストでは、OCIBindByPos()またはOCIBindByPos2()での位置パラメータの解釈が異なります。つまり、バインド・コールでの位置nは、文を左から右にスキャンしたときのn番目の一意のパラメータ名のバインドを示します。

前の例と同じSQL文のテキストを使用すると、6番目の位置を単独でバインドする場合、位置6でOCIBindByPos()またはOCIBindByPos2()をコールすることにより、:enameプレースホルダがバインドされます。バインド解除のままの場合は、:enameは、同じ名前を持つ最初のバインド・パラメータ、この場合は位置2の:enameから値を継承することになります。

OCI配列インタフェース

Oracle Databaseにデータを渡すには、様々な方法があります。

OCIStmtExecute()ルーチンを使用してSQL文を繰り返し実行し、反復のたびに異なる入力値を指定することもできます。

Oracle配列インタフェースを使用すると、単一の文とOCIStmtExecute()のコール1回のみで、多数の値を入力できます。その場合、配列を入力プレースホルダにバインドし、itersパラメータで制御して配列全体を同時に渡すことができます。

配列インタフェースを使用すると、大量のデータを更新または挿入する場合に、データベースとのラウンドトリップの回数を大幅に削減できます。この削減により、通信量の多いクライアント/サーバー環境では、大きなパフォーマンスの向上につながります。たとえば、データベースに10行挿入するアプリケーションを考えてみます。OCIStmtExecute()を10回、それぞれ1個の値を指定してコールすると、すべてのデータの挿入にネットワーク・ラウンドトリップが10回必要です。入力配列を使用すると、OCIStmtExecute()を1回コールするのみで同じ結果が得られ、ネットワーク・ラウンドトリップは1回ですみます。

Oracle Database 12cリリース2 (12.2)以上では、従来のDMLによるハイブリッド列圧縮(HCC)のサポートが追加されているため、OCIでの配列の挿入中にHCCを使用できます。HCCの従来型配列の挿入は、ASSM表領域のHCC表でのみサポートされます。

ノート:

OCI配列インタフェースを使用して挿入を行う場合は、各行が挿入されるたびに、データベース内の行トリガーが起動されます。

配列DML文で使用できる最大行数は、40億 -1 (3,999,999,999)です。ただし、ub4のかわりにub8を使用すると、配列DML文で許可される最大行数は、40億行より増加します。

関連項目:

PL/SQLのプレースホルダのバインドについて

PL/SQLブロックの処理は、単一のSQL文の場合と同じようにそのブロックを文字列変数に入れて、任意の変数をバインドし、ブロックを含む文を実行して行います。

PL/SQLブロックのプレースホルダをプログラム変数にバインドする場合は、OCIBindByName() またはOCIBindByName2()、あるいはOCIBindByPos() またはOCIBindByPos2()を使用して、スカラーまたは配列のいずれかのホスト変数に対して基本バインドを行います。

次の短い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()またはOCIBindByName2()を使用して、変数を:emp_name:salaryおよび:commissionの各出力プレースホルダにバインドし、入力プレースホルダ:emp_numberにも変数をバインドします。

ノート:

バインド・コールでッファ長を0 (ゼロ)に設定するか、あるいは対応するインジケータを-1に設定して、純OUTバッファの場合も含め、すべてのババッファを初期化する必要があります。

関連項目:

OCIバインドで使用するステップ

プレースホルダは複数のステップでバインドされます。

単純なスカラー・バインドまたは配列バインドの場合は、OCIBindByName() またはOCIBindByName2()、あるいはOCIBindByPos() またはOCIBindByPos2()を使用して、プレースホルダとデータとの関連付けを指定するのみです。

バインドが完了すると、SQL文の実行時には、入力データの位置やPL/SQL出力データを入れる位置がOCIライブラリに対して明らかになります。プログラム入力データは、プログラム変数をプレースホルダにバインドする際にプログラム変数内に存在している必要はありませんが、文を実行する際には存在している必要があります。

次の例7-1のコード例は、SQL文の各プレースホルダのハンドル割当てとバインドを示しています。

ノート:

checkerr()関数によって、OCIアプリケーションからのリターン・コードが評価されます。この関数のコードは、「OCIErrorGet()」の例にあります。

例7-1 SQL文の各プレースホルダのハンドル割当てとバインド

...
/* The SQL statement, associated with stmthp (the statement handle)
by calling OCIStmtPrepare2() */
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ブロック

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が未定義であることに注意してください。

例7-2は、単一バインド・コールのみが必要な単純なスカラー・バインドを実行する方法を示しています。場合によっては、特定のバインド・データ型または実行モードについて属性を定義するために、追加のバインド・コールが必要です。

例7-2 OCIで使用されるPL/SQL文の定義

/* 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;
  OCISession *usrhp = (OCISession *)NULL; 
...
/* attach to Oracle database, and perform necessary initializations
and authorizations */
...
      /* prepare the statement request, passing the PL/SQL text
        block as the statement to be prepared */
checkerr(errhp, OCIStmtPrepare2(svchp, &stmthp, errhp, (text *) give_raise, (ub4) 
      strlen(give_raise), NULL, 0, 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 variables 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);

OCIStmtRelease(stmthp, errhp, NULL, 0, OCI_DEFAULT);
}

関連項目:

PL/SQL OUT変数をバインドする必要があり、未定義である理由については、「PL/SQL出力変数の定義について」および「名前付きデータ型、REF定義およびPL/SQL OUTバインドの情報」を参照してください。

OCIでの拡張バインド操作

拡張バインド操作には、マルチステップ・バインド、名前付きデータ型のバインド、REFのバインドなどがあります。

「OCIのプレースホルダのバインドについて」では、OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()を使用して、SQL文のプレースホルダとプログラム変数間の関連付けを行うための基本バインド操作の方法について説明しました。この項では、マルチステップ・バインド、名前付きデータ型のバインド、REFのバインドなどの拡張バインド操作について説明します。

場合によっては、特定のバインド・データ型または特定の実行モードについて特定の属性を定義するために、追加のバインド・コールが必要です。

次の各項ではこの特別なケースについて説明し、バインドの詳細は、表7-1にまとめてあります。

表7-1 バインド・タイプの情報の概要

バインドのタイプ バインド・データ型 ノート

スカラー

スカラー・データ型

OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()を使用して、単一のスカラーをバインドします。

スカラーの配列

スカラー・データ型

OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()を使用して、スカラーの配列をバインドします。

名前付きデータ型

SQLT_NTY

レコードおよびコレクションが含まれます。

次の2つのバインド・コールが必要です。

  • OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()

  • OCIBindObject()

ブール

SQLT_BOL

OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()を使用して、ブールをバインドします。

REF

SQLT_REF

次の2つのバインド・コールが必要です。

  • OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()

  • OCIBindObject()

LOB

BFILE

SQLT_BLOB

SQLT_CLOB

OCIDescriptorAlloc()を使用してLOBロケータを割り当て、次にLOBデータ型の1つを使用して、そのアドレス(OCILobLocator **)をOCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()とバインドします。

構造体の配列または静的配列

状況により異なる

次の2つのバインド・コールが必要です。

  • OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()

  • OCIBindArrayOfStruct()

ピース単位挿入

状況により異なる

OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()が必要です。また、ピース単位コールバックを登録するために、アプリケーションでOCIBindDynamic()コールが必要な場合もあります。

REF CURSOR変数

SQLT_RSET

文ハンドル、OCIStmtを割り当ててから、SQLT_RSETデータ型を使用して、その文ハンドルのアドレスOCIStmt **をバインドします。

関連項目:

LOBのバインドについて

LOBをバインドするには、次の2つの方法があります。

  • 実際のLOB値ではなく、LOBロケータをバインドします。この場合、LOB値の書込みや読取りは、LOBロケータをOCI LOB関数に渡すことによって行われます。

  • LOBロケータを使用せず、LOB値を直接バインドします。

この項には次のトピックが含まれます:
LOBロケータのバインド

単一のバインド・コール内で、単一のロケータをバインドすることも、ロケータの配列をバインドすることもできます。

いずれの場合も、アプリケーションは、ロケータ自体ではなく、LOBロケータのアドレスを渡す必要があります。たとえば、アプリケーションにより、one_lobがLOB列に対応するバインド変数である次のSQL文が準備されたとします。

INSERT INTO some_table VALUES (:one_lob)

アプリケーションでは、次の宣言を行います。

OCILobLocator * one_lob;

例7-3のコールは、プレースホルダをバインドし、文を実行するために使用されます。

また、同一のSQL INSERT文を使用して、配列の挿入ができます。この場合、アプリケーションには、例7-4で示すコードが含まれます。

記述子は、使用する前にOCIDescriptorAlloc()関数によって割り当てる必要があります。ロケータの配列では、OCIDescriptorAlloc()を使用して、各配列要素を初期化する必要があります。BLOBCLOBおよびNCLOBを割り当てる場合は、OCI_DTYPE_LOBtypeパラメータとして使用します。BFILEを割り当てる場合は、OCI_DTYPE_FILEを使用します。

例7-3 単一のロケータを挿入するためのプレースホルダのバインドと文の実行

/* 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 */

例7-4 ロケータ配列を挿入するためのプレースホルダのバインドと文の実行

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,...);
OCIStmtExecute(...,10,...);               /* 10 is the iters parameter */

この項には次のトピックが含まれます。「LOBロケータのバインドに対する制限」

関連項目:

OCIDescriptorAlloc()
LOBロケータのバインドに対する制限

LOBロケータのバインドに対する制限について説明します。

LOBロケータをバインドする際には、次の制限を守ってください。

  • INSERTまたはUPDATEのピース単位操作およびコールバック操作は、サポートされていません。

  • FILEロケータをINSERT文またはUPDATE文のバインド変数として使用する場合は、INSERT文またはUPDATE文を発行する前に、OCILobFileSetName()を使用してディレクトリ・オブジェクトおよびファイル名で、ロケータを初期化する必要があります。

関連項目:

LOBデータのバインドについて

0 (ゼロ)以外のあらゆるサイズのLOBでINSERTUPDATEに対するバインドが可能です。

OCIBindByPos()またはOCIBindByPos2()OCIBindByName()またはOCIBindByName2()、およびPL/SQLのバインドを使用すると、データをLOB列にバインドできます。

4KBを超えるデータをLOB列にバインドする場合は、一時表領域の一部を使用します。一時表領域が少なくともLOBバインドの長さの合計値に等しい量のデータを格納できる大きさであることを確認してください。一時表領域が拡張可能であれば、既存の領域が一杯になると自動的に拡張されます。次のコマンドを使用して、拡張できる一時表領域を作成します。

CREATE TABLESPACE ... AUTOEXTEND ON ... TEMPORARY ...;
この項には次のトピックが含まれます:
LOBデータのバインドに対する制限

LOBデータのバインドに対する制限について説明します。

LOBデータをバインドする際には、次の制限を守ってください。

  • 表にLONG列とLOB列の両方がある場合は、LONG列とLOB列のどちらに対しても4KBを超えるデータのバインドが可能ですが、同一の文で両方を実行することはできません。

  • INSERT AS SELECT操作で、LOB列に対するデータのバインドはできません。

  • LONGまたはLOBのどちらでもないが、SQL文でLOBまたはLONGバインド変数の後にあるバインド変数の最大サイズについて、特別な配慮が必要です。そのようなバインド変数の最大サイズが4000バイトを超えると、ORA-24816エラーが発生します。このエラーを回避するには、文字セットの変換後、サーバー側で最大サイズが4000バイトを超えるバインドに対して、OCI_ATTR_MAXDATA_SIZEを4000バイトに設定する必要があります。または、そのようなバインドがバインド・リスト内でLONGまたはLOBの前に置かれるように、バインドの順番を変更します。

  • 4000バイトを超えるデータに対しては、HEXからRAWRAWから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 does not work because Oracle dpes not do implicit 
      -- hex to raw conversion.
      insert into t (c2) values (text);  
    
      -- The following does not work because Oracle does not do implicit 
      -- raw to hex conversion.
      insert into t (c1) values (binbuf);  
    
      -- The following does not work because you cannot 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')); 
LOBデータのバインドの例

LOBデータのバインドの例いくつか示します。

例7-5から例7-12では、次の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);

例7-5 可能: C1、C2およびL列に、それぞれサイズが最大8000、8000および2000バイトのバインド可変データ値を挿入

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);
}

例7-6 可能: C1およびL列に、それぞれサイズが最大2000および8000バイトのバインド可変データ値を挿入

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);
}

例7-7 可能: C1、C2およびL列を、それぞれサイズが最大8000、8000および2000バイトのバインド可変データ値に更新

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);
}

例7-8 可能: C1、C2およびL列を、それぞれサイズが最大2000、2000および8000バイトのバインド可変データ値に更新

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);
}

例7-9 可能: ピース単位、コールバックおよび配列挿入または更新の操作

void insert()
{
   /* Piecewise, callback and array insert/update operations similar to 
    * the allowed regular insert/update operations are also allowed */
}

例7-10 不可能: LOB列とLONG列の両方に4000バイトを超えるデータを同じINSERT文を使用して挿入

void insert()
{
   /* The following is NOT allowed because you cannot 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);
}

例7-11 可能: CT3 LOB列にサイズが最大2000バイトのバインド変数データ値を挿入

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);
}

例7-12 不可能: 選択操作としての挿入で任意の長さのデータをLOB列にバインド

void insert()
{
   /* The following is NOT allowed because you cannot 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);
}

OCI_DATA_AT_EXECモードでのバインドについて

OCIBindByName()またはOCIBindByName2()コール、あるいはOCIBindByPos()またはOCIBindByPos2()コールのmodeパラメータがOCI_DATA_AT_EXECに設定されているとき、アプリケーションで実行時にデータを提供するコールバック方式を使用する場合は、追加のOCIBindDynamic()コールが必要です。

OCIBindDynamic()のコールは、提供されたデータまたはその一部を示すために、必要に応じてコールバック・ルーチンを設定します。OCI_DATA_AT_EXECモードを選択した場合でも、コールバックのかわりに標準のOCIのピース単位ポーリング・メソッドを使用する場合は、OCIBindDynamic()コールは必要ありません。

RETURN句の変数をバインドするとき、アプリケーションでは、OCI_DATA_AT_EXECモードを使用して、コールバックを提供する必要があります。

REF CURSOR変数のバインドについて

REF CURSORは、バインド・データ型のSQLT_RSETを使用して、文ハンドルにバインドされます。

OCIでの定義の概要

問合せ文は、データベースのデータをアプリケーションに戻します。

問合せを処理するときには、データを検索する選択リストの各項目について、出力変数または出力変数の配列を定義する必要があります。定義ステップでは、戻される結果の格納場所と格納形式を判断する関連付けを行います。

たとえば、プログラムで次の文を処理するには、通常は2つの出力変数を定義する必要があります。1つは、name列から戻される値を受け取るための変数、もう1つはssn列から戻される値を受け取るための変数です。

SELECT name, ssn FROM employees
    WHERE empno = :empnum

name列の値のみを取り出す場合は、ssn列用の出力変数を定義する必要はありません。処理されるSELECT文が問合せに対して複数の行を戻す場合、出力変数はスカラー値のかわりに配列を定義できます。

定義ステップは、アプリケーションに応じて文の実行前または実行後に行います。コンパイル時に選択リスト項目のデータ型がわかっている場合には、文を実行する前に定義を行うことができます。実行時に入力する動的SQL文、あるいは明確に定義された選択リストがない文をアプリケーションで処理する場合、アプリケーションはその文を実行して、記述情報を取り出す必要があります。記述情報を取り出した後、各選択リスト項目の型情報を使用して、出力変数を定義できます。

OCIでは、クライアント側で定義コールをローカルに処理します。定義ステップでは、結果を格納するバッファの位置を指示し、さらに、データがアプリケーションに戻されるときに行う必要があるデータ変換の種類を決定します。

ノート:

出力バッファには、2バイトの位置揃えを行う必要があります。

OCIDefineByPos()またはOCIDefineByPos2()コールのdtyパラメータでは、出力変数のデータ型を指定します。OCIでは、データを出力変数にフェッチするときに広範囲のデータ変換を行うことができます。たとえば、Oracle DATE形式の内部データは、出力時に、自動的にStringデータ型に変換できます。

この項には次のトピックが含まれます。「OCIの定義に使用するステップ」

関連項目:

OCIの定義に使用するステップ

基本的な定義は、定位置コールのOCIDefineByPos()またはOCIDefineByPos2()により行われます。

このステップでは、選択リスト項目と出力変数との間の関連付けを行います。特定のデータ型またはフェッチ・モードには追加の定義コールが必要です。定義ステップが完了すると、OCIライブラリでは、データを入れる位置を決定します。SQL文を再準備または再実行することなく、定義コールを再度行って出力変数を再定義できます。

例7-13では、実行および記述操作の後にスカラー出力変数を定義しています。

関連項目:

例7-13 実行および記述操作後のスカラー出力変数の定義

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 does not 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;
  }

OCIでの拡張定義操作

この項では、マルチステップ定義、名前付きデータ型およびREFなどの拡張定義操作について説明します。

定義ステップで、OCIDefineByPos()またはOCIDefineByPos2()のコール以外の別のコールが必要になる場合があります。たとえば、配列フェッチOCIDefineArrayOfStruct()または名前付きデータ型フェッチOCIDefineObject()の属性を定義する場合です。たとえば、1列の名前付きデータ型を含む複数の行をフェッチするには、その列で3つのコールをすべて呼び出す必要があります。スカラー列のみを含む複数の行をフェッチするには、OCIDefineArrayOfStruct()およびOCIDefineByPos()またはOCIDefineByPos2()が適しています。

また、Oracle Databaseには、オブジェクト型属性をマップする事前定義済のCデータ型が用意されています。

LOB出力変数の定義について

LOBを定義するには、次の2つの方法があります。

  • 実際のLOB値ではなく、LOBロケータを定義します。この場合、LOB値の書込みや読取りは、LOBロケータをOCI LOB関数に渡すことによって行われます。

  • LOBロケータを使用せず、LOB値を直接定義します。

この項には次のトピックが含まれます:
LOBロケータの定義について

単一の定義コール内では、単一のロケータを定義することも、ロケータの配列を定義することもできます。いずれの場合も、アプリケーションは、ロケータ自体ではなく、LOBロケータのアドレスを渡す必要があります。たとえば、アプリケーションで次のSQL文が準備されたとします。

SELECT lob1 FROM some_table;

この文では、lob1はLOB列で、one_lobは次の宣言を伴ったLOB列に対応する定義変数です。

OCILobLocator * one_lob;

次のようなコールを使用して、プレースホルダをバインドし、文を実行します。

/* initialize single locator */
OCIDescriptorAlloc(...&one_lob, OCI_DTYPE_LOB...);
...
/* pass the address of the locator */
OCIBindByName(...,(void *) &one_lob,... SQLT_CLOB, ...);
OCIStmtExecute(...,1,...);                /* 1 is the iters parameter */

また、同一のSQL SELECT文を使用して、配列の挿入ができます。このケースでは、アプリケーション内に次のコードが組み込まれている必要があります。

OCILobLocator * lob_array[10];
...
for (i=0; i<10, i++)
   OCIDescriptorAlloc(...&lob_array[i], OCI_DTYPE_LOB...);
                                         /* initialize array of locators */
...
OCIBindByName(...,(void *) lob_array,...);
OCIStmtExecute(...,10,...);               /* 10 is the iters parameter */

記述子は、使用する前にOCIDescriptorAlloc()関数によって割り当てる必要があります。ロケータの配列では、OCIDescriptorAlloc()を使用して、各配列要素を初期化する必要があります。BLOBCLOBおよびNCLOBを割り当てる場合は、OCI_DTYPE_LOBtypeパラメータとして使用します。BFILEを割り当てる場合は、OCI_DTYPE_FILEを使用します。

LOBデータの定義について

0 (ゼロ)以外のあらゆるサイズのLOBでSELECTの定義が可能です。OCIDefineByPos()およびPL/SQLの定義を使用すると、使用可能な最大サイズのデータをLOB列から選択できます。1行に複数のLOBを含めることができるため、同一のSELECT文中の各LOBに対して最大サイズのデータを選択できます。

例7-14例7-15では、次のSQL文が基礎となっています。

CREATE TABLE lob_tab (C1 CLOB, C2 CLOB);

例7-14 実行前のLOBの定義

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);
}

例7-15 実行後のLOBの定義

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);
}

PL/SQL出力変数の定義について

PL/SQLブロック内のSQL SELECT文の選択リスト項目については、出力変数を定義するのに定義コールは使用しません。

かわりに、OCIバインド・コールを使用してください。

関連項目:

PL/SQL出力変数の定義の詳細は、「名前付きデータ型、REF定義およびPL/SQL OUTバインドの情報」を参照してください

ピース単位フェッチの定義について

ピース単位フェッチでは、最初にOCIDefineByPos()またはOCIDefineByPos2()をコールする必要があります。

アプリケーションで、標準のポーリング・メカニズムではなくコールバックを使用する場合は、追加のOCIDefineDynamic()コールが必要です。

OCIでの構造体配列のバインドと定義について

構造体配列の定義では、最初にOCIDefineByPos()またはOCIDefineByPos2()をコールする必要があります。

構造体配列操作に必要なskipパラメータなどの各追加パラメータを設定するには、追加のOCIDefineArrayOfStruct()コールが必要です。

構造体配列の使用により、複数行、複数列の操作の処理を簡素化できます。関連したスカラー・データ項目の構造体を作成し、データベースの値を構造体配列へフェッチしたり、これらの構造体配列の値をデータベースに挿入できます。

たとえば、あるアプリケーションでNAMEAGEおよびSALARYという列から、複数行のデータをフェッチする必要があるとします。アプリケーションでは、個別のフィールド(それぞれが、データベース表で1行に含まれているNAMEAGEおよびSALARYのデータを保持)を含む構造体の定義を組み込むことができます。アプリケーションでは、次に、これらの構造体配列へデータをフェッチします。

構造体配列を使用して、複数行、複数列の操作を実行するには、その操作に関連する各列を、構造体の中の1フィールドと関連付けておく必要があります。この関連付けは、OCIDefineArrayOfStruct()コールおよびOCIBindArrayOfStruct()コールの一部であり、データの格納場所を指定します。

スキップ・パラメータ

構造体配列間で列データを分割すると、それはデータベース内では連続した列データとして格納されなくなります。

単一の構造体配列は、いくつかのスカラー配列で構成されているかのようにデータを格納します。このため、バインドまたは定義を行う各フィールドにスキップ・パラメータを指定する必要があります。スキップ・パラメータは、構造体配列の中にある同じフィールドまでスキップするために必要なバイト数です。通常は、1つの構造体のバイト数と等しい値です。

図7-2は、スキップ・パラメータの決定方法を示しています。このケースでは、スキップ・パラメータは、field1 (2バイト)、field2 (4バイト)およびfield3 (2バイト)のサイズの合計8バイトです。このサイズは、1つの構造体のサイズと同じです。

図7-2 スキップ・パラメータの決定

図7-2の説明が続きます
「図7-2 スキップ・パラメータの決定」

オペレーティング・システムによっては、スキップ・パラメータをsizeof(struct)ではなく、sizeof(one_array_element)に設定することが必要な場合もあります。これは、コンパイラによっては、構造体に余分なバイトを挿入する場合があるからです。

ub4ub1という2つのフィールドで構成されたC構造体配列のケースを考えてみます。

struct demo {
    ub4 field1;
    ub1 field2;
};
struct demo demo_array[MAXSIZE];

コンパイラによっては、この配列内の次の構造体を開始するub4の位置を正しくするために、ub1の後に3バイトの埋込みを挿入することがあります。このようなコンパイラでは、次の文を使用すると、誤った値が戻される可能性があります。

skip_parameter = sizeof(struct demo);

一部のオペレーティング・システムでは、これで8バイトの適切なスキップ・パラメータが戻されます。その他のシステムでは、この文でskip_parameterが5に設定されます。後者の場合、スキップ・パラメータの正しい値を得るには次の文を使用します。

skip_parameter = sizeof(demo_array[0]);

この項には次のトピックが含まれます: 標準配列のスキップ・パラメータ

標準配列のスキップ・パラメータ

構造体配列は、単一変数の配列をバインドおよび定義する機能を拡張したものです。

単一変数の配列操作を指定する場合、関連したスキップ・パラメータは、その配列のデータ型のサイズと等しくなります。たとえば、配列を次のように宣言したとします。

text emp_names[4][20];

バインドまたは定義操作のスキップ・パラメータは20になります。配列の各データ要素は、構造体の一部としてでなく、別個の単位として認識されます。

構造体配列で使用されるOCIコール

構造体配列に関係する操作を行う際に使用するコールについて説明します。

構造体配列に関係する操作を行う際には、次の2つのOCIコールを使用する必要があります。

  • OCIBindArrayOfStruct()は、入力変数の構造体配列でフィールドをバインドする場合に使用します。

  • OCIDefineArrayOfStruct()は、出力変数の構造体配列を定義する場合に使用します。

    ノート:

    構造体の配列のバインドまたは定義には、複数のコールが必要です。OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()のコールはOCIBindArrayOfStruct()のコールに先行し、OCIDefineByPos()またはOCIDefineByPos2()のコールはOCIDefineArrayOfStruct()のコールに先行する必要があります。

構造体配列と標識変数

構造体配列を実装することによって、標識変数とリターン・コードの使用も可能になります。

フェッチ、挿入または更新された情報の配列に対応して、列レベルの標識変数とリターン・コードのパラレル配列を宣言できます。これらの配列には、独自のスキップ・パラメータを含めることができ、スキップ・パラメータは、OCIBindArrayOfStruct()またはOCIDefineArrayOfStruct()コール中に指定します。

プログラム値および標識変数の構造配列を設定する方法は多数あります。データベースの3列のデータを、3つのフィールドを含む構造配列にフェッチするアプリケーションについて考えてみます。この場合、3つのフィールドの標識変数の構造に対応する配列を設定でき、データベースからフェッチされるいずれかの列に対してそれぞれが列レベルの標識変数になります。インジケータ構造体内のフィールドと選択リスト項目数の間に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()またはOCIBindByPos2()、あるいはOCIBindByName()またはOCIBindByName2()関数でmodeパラメータに値OCI_IOVを使用します。modeにこの値を指定する場合は、パラメータvaluepOCIIOVのアドレスを渡す必要があります。データ型のサイズは、パラメータvalueszで渡す必要があります。たとえば、次のようにします。

OCIIOV vecarr[NumBuffers];
...
/* For bind at position 1 with data type int     */
OCIBindByPos(stmthp, bindp, errhp, 1, (void *)&vecarr[0],
             sizeof(int), ... OCI_IOV);
...

複数バッファを定義する場合、OCIDefineByPos()またはOCIDefineByPos2()関数でmodeパラメータにOCI_IOVを使用します。modeにこの値を指定する場合は、パラメータvaluepOCIIOVのアドレスを渡します。データ型のサイズは、パラメータvalueszで渡す必要があります。このモードは、分散または集約のバインドに使用されることが目的であり、複数のバッファを1つの位置にバインドまたは定義できます。たとえば、列Aの最初の10行を1つのバッファに、次の5行を1つのバッファに、そして残りの25行を別のバッファにバインドまたは定義できます。これにより、配列の実行操作を行う場合に、それらのすべてを1つの大きなバッファに割り当ててコピーする必要がなくなります。

例7-16は、構造体OCIIOVとそのmode値の使用例です。

例7-16 複数のバインドおよび定義バッファの使用

/* The following macros mention the maximum length of the data in the 
 * different buffers. */
 
#define    LENGTH_DATE      10
#define    LENGTH_EMP_NAME  100
 
 
/* These two 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 or Define calls.
   buf_4 and buf_5 above have the same data values; that is, LENGTH_EMP_NAME
   although 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. You 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, and so on, and then 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 in 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 in 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.  Note that the buffer where you receive
the data is split into two locations, 
each having NUM_DEFINE number of elements. However, the indicator
buffer, the actual length buffer, and the return code buffer comprise a
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でのRETURNING句を使用したDML

この項では、RETURNING句を使用してDML文を正しく実装するためのルールについて、その概要を説明します。

OCIでは、SQL INSERT文、UPDATE文およびDELETE文でのRETURNING句の使用をサポートします。

この項には次のトピックが含まれます:

関連項目:

2つのSQL文を結合するためのDMLでのRETURNING句の使用について

DML文でRETURNING句を使用することにより、2つのSQL文を1つに結合でき、サーバー・ラウンドトリップを削減できます。

これは、追加の句を従来のUPDATE文、INSERT文およびDELETE文に追加することで実行できます。追加の句では、問合せをDML文に効果的に追加します。

OCIでは、OUTバインド変数として値がアプリケーションに戻されます。次の例では、バインド変数の前にコロン":"が付いています。これらの例では、col1col2および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

RETURNING...INTO変数のバインドについて

UPDATE文およびDELETE文は表内の複数行に影響し、DML文は1回のOCIStmtExecute()コールで複数回実行できるため、どれだけのデータが戻ってくるのかを実行時に判断できない場合があります。

そのため、RETURNING...INTOプレースホルダに対応する変数は、OCI_DATA_AT_EXECモードでバインドする必要があります。アプリケーションでは、ポーリング・メカニズムを使用するのではなく、アプリケーション自体の動的データ処理コールバックを定義する必要があります。

RETURNING句は、LOBを操作するときに特に役立ちます。通常、アプリケーションでは、空のLOBロケータをデータベースに挿入し、再度選択して操作を行います。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()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()を使用して、OCI_DATA_AT_EXECモードでRETURNING句のプレースホルダをバインドした後で、各プレースホルダに対してOCIBindDynamic()をコールします。

  • RETURNING句のプレースホルダをバインドする場合は、OCIBindDynamic()コールのocbfpパラメータとして、有効なOUTバインド関数を指定します。この関数では、戻されたデータを保持するための記憶域が必要です。

  • OCIBindDynamic()コールのicbfpパラメータによって、コール時にNULL値を戻すデフォルトの関数を提供する必要があります。

  • OCIBindDynamic()piecepパラメータを、OCI_ONE_PIECEに設定します。

RETURNING句を使用したDML文では、重複バインドは使用できず、文のDMLセクションのバインド変数と、RETURNINGセクションのバインド変数の間では重複は認められません。

ノート:

OCIでは、RETURNING句バインドのコールバック・メカニズムのみがサポートされています。ポーリング・メカニズムはサポートされていません。

OCIエラー処理

OCIBindDynamic()に提供されたOUTバインド関数には、エラー時に文の結果の一部を受け取る準備が必要です。

10回実行されるDML文をアプリケーションで発行し、5回目の実行中にエラーが発生した場合、Oracle Databaseは、1回目から4回目までのデータを戻します。コールバック関数は、最初の4回のデータを受け取るためにさらにコールされます。

OCIでのRETURNING REF...INTO句を使用したDML

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つのステップが必要です。

例7-17の次の疑似コードは、前の3ステップに必要なバインドを実行する関数を示します。

  1. 初期バインド情報を、OCIBindByName()またはOCIBindByName2()を使用して設定します。
  2. REF (型の記述オブジェクト(TDO)を含む)に対する追加のバインド情報は、OCIBindObject()を使用して設定します。
  3. OCIBindDynamic()に対するコールを作成します。

例7-17 OCIアプリケーションでのREF出力変数のバインド

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コールバックに関するその他のノート

コールバック関数がコールされるとき、バインド・ハンドルのOCI_ATTR_ROWS_RETURNED属性により、アプリケーションでは、ある特定の反復処理で戻される行数がわかります。

最初のコールバックの反復処理中に、そのバインド変数に対して戻されるすべての行に領域を割り当てることができます。同一の反復処理内で後続のコールバックを実行しているときは、割り当てられた領域内の正確なメモリーまでバッファ・ポインタを増やします。

OCIでのDML RETURNING文に対する配列インタフェース

OCIでは、各反復処理で複数の行を戻す単一行DML操作および配列DML操作に追加の機能性を提供します。

この機能を利用するには、バインド・コールのOUTバッファを、OCIStmtExecute()コールで指定した反復件数以上の値に指定する必要があります。これは、コールバックを介してバインド・バッファに提供される追加機能です。

いずれかの反復処理で複数の行が戻された場合は、アプリケーションにOCI_SUCCESS_WITH_INFOリターン・コードが戻されます。この場合、DML操作は正常に完了しています。このとき、アプリケーションでは、トランザクションをロールバックするか、警告を無視します。

関連項目:

OCIStmtExecute()

OCIバインドおよび定義における文字変換

この項では、クライアントとサーバー間での文字変換に関する問題について説明します。

文字セットの選択について

文字データを含むデータベースの列がCHARVARCHAR2NCHARまたはNVARCHAR2列として定義されている場合、その列に関連するバインドまたは定義では、文字セット仕様の処理について特別な配慮が必要です。

クライアントとサーバーの文字セットの幅が異なる場合、および適切に文字変換を行うために、これらの考慮が必要です。異なる文字セット間でデータを変換する間に、データ・サイズが、最大で4倍に増加したり4分の1に縮小する場合があります。データを保存するためのバッファのサイズを十分に確保してください。

Oracle Database 12cリリース2 (12.2)以上では、OCIに2つのサービス・コンテキスト・ハンドル属性のOCI_ATTR_MAX_CHARSET_RATIOおよびOCI_ATTR_MAX_NCHARSET_RATIOが用意されており、それぞれサーバー文字セットからクライアント文字セットまたは各国語文字セットへの最大文字セット拡張率を取得できます。これらの属性を使用すると、変換の前に最適なメモリー・バッファを効率的に割り当てることができるため、データがデータベースから戻されたときにそれを保持するための十分な領域を割り当てることができます。サーバーとクライアント間の文字セットまたは各国語文字セットが異なる場合に、これらの属性を使用すると便利です。

次のコード例は、サーバー文字セットからクライアント文字セットへの最大文字セット拡張率を取得する方法を示しています。
size_t   cratio;
OCIAttrGet((void *)svchp, (ub4)OCI_HTYPE_SVCCTX, (size_t *)&cratio, (ub4) 0, OCI_ATTR_MAX_CHARSET_RATIO, errhp);
printf("Conversion ratio from server to client character set is %d\n", cratio);

Conversion ratio from server to client character set is 2
次のコード例は、サーバー文字セットから各国語クライアント文字セットへの最大文字セット拡張率を取得する方法を示しています。
size_t   cratio;
OCIAttrGet((void *)svchp, (ub4)OCI_HTYPE_SVCCTX, (size_t *)&cratio, (ub4) 0, OCI_ATTR_MAX_NCHARSET_RATIO, errhp);
printf("Conversion ratio from server to client ncharset is %d\n", cratio); 

Conversion ratio from server to client ncharset is 1

アプリケーションでは、CHARVARCHAR2、またはNCHARNVARCHAR2データをバイト数(通常の場合)ではなく文字数で処理する方が簡単な場合があります。

文字セット・フォームと文字セットID

各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で指定された値です。

ノート:

  • クライアント側の文字セットIDに関係なく、データはサーバーのデータベース文字セットIDまたは各国語文字セットIDに従って変換され、データベースに挿入されます。

  • OCI_ATTR_CHARSET_IDに0 (ゼロ)を設定することはできません。

  • 定義ハンドル属性OCI_ATTR_CHARSET_FORMおよびOCI_ATTR_CHARSET_IDは、LOBタイプには影響しません。サーバーからフェッチされたLOBロケータは、元のcsformを保持します。これらの属性に基づく定義変換の一部として、CLOB/NCLOB変換は行われません。

関連項目:

CHARとNCHARの間の暗黙的な変換

データベース文字セットと各国語文字セット間での暗黙的な変換の結果、OCIでは、CHARNCHARの間でのクロスバインドおよびクロス定義に対するサポートが可能となりました。

OCI_ATTR_CHARSET_FORM属性がSQLCS_NCHARに設定されている場合でも、OCIでは、データがCHAR列に挿入されると、データをデータベース文字セットに変換できます。

OCIでのクライアント文字セットの設定について

クライアント文字セットは、OCIEnvNlsCreate()関数のcharsetパラメータおよびncharsetパラメータを使用して設定できます。

両方のパラメータともOCI_UTF16IDを設定できます。charsetパラメータでは、メタデータおよびCHARデータのコーディングが制御されます。ncharsetパラメータでは、NCHARデータのコーディングが制御されます。OCINlsEnvironmentVariableGet()関数は、NLS_LANGの文字セットおよびNLS_NCHARの各国語文字セットを戻します。

例7-18は、これらの関数の使用例です(OCIには、UTF-16データのバインドと定義を容易にするために、utextと呼ばれるtypedefが用意されています)。

例7-18 OCIでのクライアント文字セットのOCI_UTF16IDへの設定

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での変数のバインドについて

更新操作または挿入操作は、変数のバインドによって行われます。

変数をバインドするときは、バインド・ハンドルのOCI_ATTR_MAXCHAR_SIZE属性およびOCI_ATTR_MAXDATA_SIZE属性を指定して、Oracle Databaseにデータを挿入するときに使用するバイト制約と文字制約を指定します。

これらの属性は、次のように定義します。

  • OCI_ATTR_MAXDATA_SIZE属性では、サーバー側のバッファで許容される最大バイト数を設定します。

  • 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 (宛先列)への変換

    データに対して暗黙的バインド変換が行われます。ソース・バッファ・サイズに、クライアント側とOracle Database側の文字セット間の最悪の膨張率を乗算した値を、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_MAXCHAR_SIZE属性を使用して、Oracle Databaseに確保する文字数を設定し、バインド・データを格納します。

たとえば、OCI_ATTR_MAXDATA_SIZEが100に設定され、OCI_ATTR_MAXCHAR_SIZEが0 (ゼロ)に設定されている場合、Oracle Databaseでのデータの変換後に可能な最大サイズは100バイトです。ただし、OCI_ATTR_MAXDATA_SIZEが300に設定され、OCI_ATTR_MAXCHAR_SIZEが100などの0 (ゼロ)以外の値に設定されている場合で、文字セットに2バイトまたは2文字がある場合は、割当て可能な最大サイズは200バイトになります。

定義については、クライアント・アプリケーションのバッファに戻すことができる最大文字数をOCI_ATTR_MAXCHAR_SIZE属性で指定します。導出されたバイト長は、OCIDefineByPos()またはOCIDefineByPos2()コールで指定されたmaxlengthパラメータを上書きします。

ノート:

バインド・コールまたは定義コールで指定されたバッファ長は、OCI_ATTR_MAXCHAR_SIZE属性の値に関係なく、常にバイト数による長さです。また、送受信する実際の長さもバイト数になります。

関連項目:

OCIDefineByPos()またはOCIDefineByPos2()

OCIバインド時のバッファの拡張

OUTバインドまたはPL/SQLバインドの場合は、OCI_ATTR_MAXDATA_SIZEを設定しないでください。OCI_ATTR_MAXDATA_SIZEは、INSERT文またはUPDATE文の場合のみ設定してください。

いずれの属性も設定されていない場合、OCIでは最適な見積りを行ってバッファを拡張します。

この項には次のトピックが含まれます:
INバインド

INバインドの場合、基礎となる列が文字長セマンティクスを使用して作成されている場合は、OCI_ATTR_MAXCHAR_SIZEを使用して制約を指定することをお薦めします。

実際のバッファに含まれる文字数がOCI_ATTR_MAXCHAR_SIZEで指定した文字数より少なければ、OCIレベルでの制約違反は発生しません。

基礎となる列がバイト長セマンティクスを使用して作成されている場合は、バインド・ハンドルのOCI_ATTR_MAXDATA_SIZEを使用して、サーバーでのバイト制約を指定します。OCI_ATTR_MAXCHAR_SIZEの値も指定している場合は、この制約がサーバー側の受信バッファの割当て時に適用されます。

動的SQL

動的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を、実際の列幅より少ない値(バイト数または文字数)に設定することをお薦めします。

挿入時のバッファの拡張

OCI_ATTR_MAXDATA_SIZEを使用して、挿入時のバッファ拡張による予期しない動作の発生を回避します。

データベース列に文字長セマンティクスがあり、その列にユーザーがOCIBindByPos()またはOCIBindByPos2()、あるいはOCIBindByName()またはOCIBindByName2()を使用してデータの挿入を試み、OCI_ATTR_MAXCHAR_SIZEのみを3000バイトに設定したとします。データベース文字セットはUTF8で、クライアント文字セットはASCIIです。この場合、クライアント側ではサイズが3000バイトのバッファに3000文字が入りますが、サーバー側ではバッファが拡張されて4000バイトを超えることになります。基礎となる列がLONG型またはLOB型でないかぎり、サーバーでエラーが発生します。この問題を回避するには、OCI_ATTR_MAXDATA_SIZEを4000に指定して、Oracle Databaseが4000バイトを決して超えないことを保証します。

定義時の制約チェック

OCIでは、列からクライアント・バッファへのデータ選択に事前定義の変数が使用されます。

OCI_ATTR_MAXCHAR_SIZE値を設定して、その定義バッファに対して文字長の追加の制約を適用できます。バイト単位のバッファ・サイズはバイト長の制限として機能するため、定義ハンドルに対するOCI_ATTR_MAXDATA_SIZE属性はありません。OCIDefineByPos()またはOCIDefineByPos2()コールで指定された定義バッファのサイズをバイト制約として使用できます。

この項には次のトピックが含まれます:

関連項目:

OCIDefineByPos()またはOCIDefineByPos2()

動的SQLでの選択

動的SQLのバッファ・サイズを指定するときは、切捨てによるデータの損失を回避するために、常に暗黙的な記述でOCI_ATTR_DATA_SIZE値を使用します。

データベース列が文字長セマンティクスを使用して作成されている場合(OCI_ATTR_CHAR_USED属性によって判明します)は、OCI_ATTR_MAXCHAR_SIZE値を使用して、定義バッファに追加の制約を設定できます。OCI_ATTR_MAXCHAR_SIZE値の最大文字数がバッファに配置されます。

戻される長さ

次の長さの値は、データベースの文字長セマンティクスに関係なく、常にバイト単位で戻されます。

  • alenで戻される値、つまり、バインドと定義での実際の長さフィールド

  • VARCHARLONG VARCHARなど、特殊なデータ型の前に付けられる長さの値

  • 切捨てが発生した場合のための標識変数の値

この規則の唯一の例外は、OCI_UTF16ID文字セットIDの文字列バッファで、戻される長さはUTF-16単位になります。

ノート:

バインド・コールと定義コールのバッファ・サイズ、OCIStmtGetPieceInfo()OCIStmtSetPieceInfo()のピース・サイズおよびコールバックは常にバイト単位になります。

OCIでの文字長セマンティクスの互換性に関する一般的な問題

OCIでの文字長セマンティクスは、Oracle Databaseのリリース(リリース9.0以上とリリース8.1以下)に依存します。

  • リリース8.1以下のOracle Databaseと通信するリリース9.0以上のクライアントの場合は、OCI_ATTR_MAXCHAR_SIZEはOracle Databaseでは認識されないため、この値は無視されます。この値のみを指定した場合、OCIでは、クライアント側文字セットの各文字の最大バイト数に基づいて、対応するOCI_ATTR_MAXDATA_SIZE値が導出されます。

  • リリース9.0以上のOracle Databaseと通信するリリース8.1以下のクライアントの場合は、クライアントでOCI_ATTR_MAXCHAR_SIZE値を指定できないため、Oracle Databaseではクライアントが常にバイト長セマンティクスを使用しているものとみなします。これは、クライアントでOCI_ATTR_MAXDATA_SIZEのみを指定した場合に類似しています。

いずれの場合も、Oracle Databaseとクライアントは適切な方法で情報を交換できます。

OCI_ATTR_MAXCHAR_SIZEを使用した挿入と選択のコード例

列がN個の文字を指定して作成されると、データベースでの実際の割当てでは、最悪のケース・シナリオが検討されます。

この例を例7-19に示します。割り当てられる実際のバイト数は、Nの倍数、つまりNM倍です。現在、UTF8では各文字に割り当てられる最大バイト数として、Mが3に設定されています。

たとえば、例7-19では、EMP表で、ENAME列が30文字として定義され、ADDRESS列が80文字として定義されています。これに対応するデータベースでのバイト長はそれぞれ、M×30または3×30 = 90、およびM×80または3×80 = 240になります。

例7-19 OCI_ATTR_MAXCHAR_SIZE属性を使用した挿入および選択操作

...
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);
...
UTF-16のバインドと定義のコード例

CHARまたはVARCHAR2のバインドおよび定義、またはNCHARまたはNVARCHAR2改良型ハンドルにおける文字セットIDは、すべてのデータがUTF-16 (Unicode)エンコーディングで渡されるものとして設定できます。UTF-16を指定するには、OCI_ATTR_CHARSET_ID = OCI_UTF16IDを設定します。

OCIには、UTF-16データのバインドと定義を容易にするため、utextと呼ばれるtypedefが用意されています。utextの内部表現は、16ビットの符号なし整数(ub2)です。wchar_tデータ型のコード体系がUTF-16に準拠しているオペレーティング・システムでは、キャスト演算子を使用してutextwchar_tデータ型に容易に変換できます。

UTF-16データの場合でも、バインド・コールと定義コールでのバッファ・サイズはバイト単位とみなされます。utextデータ型は、入出力データのバッファとして使用してください。

例7-20では、UTF-16データのバインドと定義を説明する擬似コードを示しています。

例7-20 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

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()をコールして、バインドのための文ハンドルを割り当て、次のコード例で:cursor1stm2pにバインドされるように、プレースホルダ:cursor1を文ハンドルにバインドします。

例7-21のこのコードでは、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番目の位置がネストした表で、例7-22に示すように、OCIアプリケーションで文ハンドルとして定義できます。

実行後、行をstm2pにフェッチすると、有効な文ハンドルになります。

ノート:

複数のREF CURSORを取り出した場合は、stm2pへのフェッチをいつ行うか注意する必要があります。1つ目をフェッチすると、そのデータを取り出すためにフェッチを実行できます。ただし、2番目のREF CURSORstm2pにフェッチすると、最初のREF CURSORからのデータにはアクセスできなくなります。

OCIでは、スクロール・モードで実行されたPL/SQL REF CURSORはサポートしていません。

OCIでは、REF CURSORによってすでにフェッチされている行にスクロールして戻ることはできないため、スクロール可能REF CURSORはサポートされていません。

例7-21 プレースホルダ:cursor1を文ハンドルstm2pにREF CURSORとしてバインド

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);

例7-22 ネストした表(2番目の位置)を文ハンドルとして定義

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);

関連項目:

OCIHandleAlloc()

パッケージ型を含むすべてのPL/SQL型の固有の記述およびバインド

Oracle Databaseリリース12.1より、OCIクライアントでは、すべてのPL/SQL型を固有に記述およびバインドできます。

これには、以前はバインド型としてサポートされていなかった基本スカラー型ブールも含まれます。また、名前付きレコードまたはコレクション型(ネストした表、VARRAYおよび索引表を含む)など、PL/SQLパッケージで宣言された型、あるいはPL/SQLパッケージ仕様の中で宣言された暗黙的なレコードのサブタイプ(%rowtype)も含まれます。これらの機能を固有にサポートすることにより、用意されたクライアント側のAPIのみを使用して、クライアントでPL/SQL型を記述およびバインドできるようになります。

これらのデータ型(ブール、レコード、索引付きBINARY_INTEGERPLS_INTEGERまたはBINARY_INTEGER)のPL/SQL型コードは、表5-10に記載されています。これらのPL/SQL型コードの同等のSQLT型は表5-11に記載されています。クライアントでそれぞれ指定のSQLT型の値をバインドのDTYとして使用して指定の型がバインドされるようにする必要があります。たとえば、クライアントでレコードの場合はSQLT_NTYをバインドのDTYとして使用してパッケージのレコード型(OCI_TYPECODE_RECORD)をバインドする、コレクションの場合はSQLT_NTYをバインドのDTYとして使用してすべてのパッケージのコレクション型(OCI_TYPECODE_ITABLE)をバインドする、ブールの場合はSQLT_BOLをバインドのDTYとして使用してブール型(OCI_TYPECODE_BOOLEAN)がバインドされるようにする必要があります。バインドAPI OCIBindByName()OCIBindByName2()OCIBindByPos()およびOCIBindByPos2()では、これらのPL/SQL型コードを表現するバインドのDTYに含まれている各SQLT型値がサポートされます。

OCIでのランタイム・データ割当てとピース単位操作

OCIを使用して、データのピース単位の挿入、更新およびフェッチを実行できます。

また、OCIを使用すると、バインド値の静的配列を提供するかわりに、配列の挿入または更新の際に動的にデータを提供できます。非常に大きな列を小さいサイズの一連のチャンクとして挿入または取得することにより、クライアント側のメモリー要件を最小限にできます。

個々のピースのサイズは、実行時にアプリケーションにより判断され、統一することもできます。

OCIのピース単位機能は、文字列やバイナリ・データの膨大なブロックで操作(CLOBBLOBLONGRAWLONG RAWなどのデータを格納するデータベース列に関係する操作など)を行う際に特に有効です。

最後のOCIStmtFetch2()コールがOCI_SUCCESSの値を戻すと、ピース単位フェッチが完了します。

ピース単位のフェッチおよび挿入では、操作を正常に完了するために必要なコールの順序を理解することが重要です。ピース単位挿入では、コールバックが使用されない場合、挿入するピースの数よりも1回多くOCIStmtExecute()をコールする必要があります。これは、最初のOCIStmtExecute()コールが、挿入される最初のピースが必要であることを示す値を戻すからです。結果として、n個のピースを挿入する場合は、OCIStmtExecute()を合計n+1回コールする必要があります。

同様に、ピース単位フェッチを実行するときは、フェッチするピースの数より1回多くOCIStmtFetch2()をコールする必要があります。

ピース単位操作に有効なデータ型

一部のデータ型のみが、ピース単位で操作できます。

次のデータ型は、OCIアプリケーションでピース単位のフェッチ、挿入または更新を実行できます。

  • VARCHAR2

  • STRING

  • LONG

  • LONG RAW

  • RAW

  • CLOB

  • BLOB

すべてのデータ型に対してこの機能を使用する別な方法として、配列の挿入または更新のためにデータを動的に提供する方法があります。ピース単位操作をサポートしないデータ型に対しては、コールバックで、そのコールバックのpiecepパラメータのOCI_ONE_PIECEを常に指定する必要があります。

ピース単位操作の種類

ピース単位操作を実行できる方法について説明します。

ピース単位操作は、次の2通りの方法で実行できます。

  • ポーリング・パラダイムに基づいてピース単位操作を実行するには、OCIライブラリで提供されているコールを使用します。

  • 必要な情報およびデータ・ブロックを提供するには、ユーザー定義のコールバック関数を使用します。

OCIBindByPos()またはOCIBindByPos2()、あるいはOCIBindByName()またはOCIBindByName2()コールのmodeパラメータをOCI_DATA_AT_EXECに設定すると、OCIアプリケーションでは実行時にINSERTまたはUPDATE操作のためのデータが動的に提供されます。

同様に、OCIDefineByPos()またはOCIDefineByPos2()コールのmodeパラメータをOCI_DYNAMIC_FETCHに設定した場合は、フェッチ時にデータを受け取るための割当てスペースが動的に提供されます。

どちらの場合でも、コールバック関数またはピース単位操作のいずれかの方法によって、INSERTUPDATEまたはFETCH操作のランタイム情報を提供できます。コールバックを使用するときはコールバックを登録するために、追加のバインド・コールまたは定義コールが必要です。

次項以降では、挿入、更新およびフェッチのためのデータ割当てとピース単位操作について具体的に説明します。

ノート:

ピース単位操作は、SQLブロックおよびPL/SQLブロックでも有効です。

実行時のINSERTまたはUPDATEデータの提供について

OCIBindByPos()またはOCIBindByPos2()コール、あるいはOCIBindByName()またはOCIBindByName2()コールで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()コールで小さいサイズを指定する必要があります。

その手順は図7-3で示すとおりで、図の次に各ステップを説明しています。

図7-3 ピース単位挿入の実行

図7-3の説明が続きます
「図7-3 ピース単位挿入の実行」の説明
  1. OCI環境を初期化して必要なハンドルを割り当て、サーバーに接続してユーザーを認証し、OCIStmtPrepare2()を使用して文の要求を準備します。
  2. OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos() またはOCIBindByPos2()を使用して、プレースホルダをバインドします。使用するピースの実際のサイズを指定する必要はありませんが、実行時に提供できるデータの全体のサイズを指定します。
  3. 初めてOCIStmtExecute()をコールします。ここではデータは挿入されず、アプリケーションにOCI_NEED_DATAエラー・コードが戻されます。その他の値が戻された場合は、エラーが発生しています。
  4. OCIStmtGetPieceInfo()をコールし、挿入する必要があるピースの情報を取り出します。OCIStmtGetPieceInfo()のパラメータには、必要なピースが最初のピース(OCI_FIRST_PIECE)なのか、後続のピース(OCI_NEXT_PIECE)なのかを示す値を戻すポインタが含まれています。
  5. アプリケーションは、挿入するデータのピースをバッファに移し、次のパラメータを使用してOCIStmtSetPieceInfo()をコールします。
    • ピースへのポインタ

    • ピースの長さへのポインタ

    • これが最初のピース(OCI_FIRST_PIECE)、中間のピース(OCI_NEXT_PIECE)または最後のピース(OCI_LAST_PIECE)のいずれであるかを示す値

  6. 再度、OCIStmtExecute()をコールします。ステップ5でOCI_LAST_PIECEが示され、OCIStmtExecute()OCI_SUCCESSを戻した場合は、すべてのピースが正常に挿入されています。OCIStmtExecute()OCI_NEED_DATAが戻された場合は、ステップ3に戻り、次の挿入を行います。OCIStmtExecute()がこれ以外の値を戻した場合は、エラーが発生しています。

最後のピースが正常に挿入されると、ピース単位操作は完了します。これは、最後の OCIStmtExecute()コールによるOCI_SUCCESS戻り値によって示されます。

ピース単位更新も同様の方法で実行します。ピース単位更新操作では、更新対象のデータが挿入バッファに移され、OCIStmtExecute()がコールされて更新が実行されます。

PL/SQLでのピース単位操作

OCIアプリケーションでは、前に概説した方法と似た方法で、INOUTおよびIN/OUTバインド変数について、PL/SQLでのピース単位操作を実行できます。

PL/SQL文のすべてのプレースホルダは、定義ではなくバインドされることに注意してください。OCIBindDynamic()のコールでは、OUTパラメータまたはIN/OUTパラメータについて、適切なコールバックを指定します。

関連項目:

OCIBindDynamic()

PL/SQL索引付き表のバインドのサポート

PL/SQL索引付き表は、OCIを使用してPL/SQL無名ブロックにIN/OUTバインドとして渡すことができます。

PL/SQL索引付き表をバインドする手順は、SQL文に対して配列バインドを実行する手順に非常によく似ています。OCIプログラムでは、OCIBindByName()またはOCIBindByName2()、あるいはOCIBindByPos()またはOCIBindByPos2()のいずれかを使用して、次のとおりに配列の場所を配列の他のメタデータにバインドする必要があります。C配列をPL/SQL索引付き表バインド変数にバインドするプロセスでは、バインド・コール時に次の情報を指定する必要があります。

  • void *valuep (IN/OUT): クライアント・メモリーで配列の始まりを指定する場所のポインタ

  • ub2 dty (IN): クライアントで表されている配列の要素のデータ型

  • sb4 value_sz (IN): クライアントで表されている配列の各要素の最大サイズ(バイト単位)

  • ub4 maxarr_len (IN): 配列で存続期間中に保持されるデータ型の最大要素数

    静的バインド実行のために前もって配列全体を割り当てる場合、配列をmaxarr_len数の要素(それぞれのサイズがvalue_sz)を含むのに十分なサイズに設定する必要があります。この情報は、PL/SQLで表示されるように索引付き表に制約をかけるためにも使用されます。PL/SQLでは、指定したこの制限を越えて索引付き表を(読取りまたは書込みのいずれかのために)参照することはできません。

  • ub4 *curelep (IN/OUT): 現在有効な配列内の要素の数(配列の最初から)のポインタ。

    これは、最大配列長以下にする必要があります。この情報は、PL/SQLで表示されるように索引付き表に制約をかけるためにも使用されることに注意してください。INバインドの場合、PL/SQLは指定したこの制限を越えて索引付き表から読み取ることはできません。OUTバインドの場合、PL/SQLはこの制限を越えて索引付き表に書き込むことはできますが、maxarr_len制限を越えることはできません。

IN索引付き表バインドの場合、OCIStmtExecute()を実行する前に、ユーザーはその実行のために現在の配列長(*curelep)を設定する必要があります。さらに、ユーザーは、配列の要素ごとに、実際の長さとインジケータを規定どおりに設定することも必要です。

OUTバインドの場合、OCIでは配列の要素ごとに、現在の配列長(*curelep)と実際の長さ、インジケータおよびリターン・コードを規定どおりに戻す必要があります。

最高のパフォーマンスを得るには、配列を最大配列長が割り当てられたままにして、実際にやり取りされている要素の数に基づいて、実行と実行の間に現在の配列長を変更します。この方法だと、実行ごとに割当ての解除と再割当てを繰り返す必要はなく、その結果アプリケーション全体のパフォーマンスが向上します。

また、PL/SQL索引付き表に対するOCIのピース単位のコールを使用して、バインドすることもできます。この方法では、配列全体を前もって割り当てる必要はありません。OCIStmtSetPieceInfo()コールとOCIStmtGetPieceInfo()コールを使用すれば、個々の要素をピース単位で渡すこともできます。

この項には次のトピックが含まれます。「PL/SQL索引付き表バインド・インタフェースの制限」

PL/SQL索引付き表のバインド・インタフェースの制限

PL/SQL索引付き表のOCIバインド・インタフェースの制限について説明します。

PL/SQL索引付き表のOCIバインド・インタフェースでは、次のバインドがサポートされません。

  • ADTまたはREFの配列

  • LOB記述子、ROWID記述子、日時または時間隔記述子などの記述子型の配列

  • PLSQLレコード型の配列

実行時のフェッチ情報の提供について

modeパラメータにOCI_DYNAMIC_FETCHを設定してOCIDefineByPos()またはOCIDefineByPos2()をコールすると、アプリケーションでは、フェッチ時のデータ・バッファに関する情報を指定できます。

また、OCIDefineDynamic()をコールして、データ・バッファに関する情報を取得するために呼び出すコールバック関数を設定する必要がある場合があります。

ランタイム・データは、次の2つの方法のいずれかで提供されます。

  • OCIDefineDynamic()関数を使用したコールバックを定義できます。実行時に提供されるデータの最大サイズは、value_szパラメータによって定義されます。クライアント・ライブラリで、フェッチ済データを戻すためのバッファが必要な場合は、このコールバックが呼び出され、ピースまたは全体のデータを戻すための実行時バッファが提供されます。

  • コールバックが1つも定義されていない場合は、OCI_NEED_DATAエラー・コードが戻り、クライアント・アプリケーションでOCIStmtSetPieceInfo()を使用して、OUTデータ・バッファまたはピースを提供します。OCIStmtGetPieceInfo()コールにより、どの定義およびピースが含まれるかについての情報が提供されます。

この項には次のトピックが含まれます。「ピース単位フェッチの実行」

ピース単位フェッチの実行

フェッチ・バッファは、任意のサイズにすることができます。また、フェッチする各ピースは、同じサイズである必要はありません。

ただし、最後にフェッチするサイズは、残っている最後のピースに一致する必要があります。フェッチする各ピースのサイズは、OCIStmtSetPieceInfo()コールでそれぞれ設定します。そのプロセスは図7-4で示すとおりで、図の次に各ステップを説明しています。

図7-4 ピース単位フェッチの実行

図7-4の説明が続きます
「図7-4 ピース単位フェッチの実行」の説明
  1. OCI環境を初期化して必要なハンドルを割り当て、データベースに接続してユーザーを認証し、文を準備してからOCIStmtExecute()を使用して文を実行します。
  2. modeOCI_DYNAMIC_FETCHに設定し、OCIDefineByPos()またはOCIDefineByPos2()を使用して出力変数を定義します。この時点では、使用するピースの実際のサイズを指定する必要はありませんが、実行時にフェッチするデータの全体のサイズを指定します。
  3. 初めてOCIStmtFetch2()をコールします。データは取得されず、アプリケーションにOCI_NEED_DATAエラー・コードが戻されます。その他の値が戻された場合は、エラーが発生しています。
  4. OCIStmtGetPieceInfo()をコールし、フェッチするピースに関する情報を取得します。piecepパラメータは、そのピースが最初のピース(OCI_FIRST_PIECE)であるか、その後続のピース(OCI_NEXT_PIECE)であるか、または最後のピース(OCI_LAST_PIECE)であるかを示します。
  5. OCIStmtSetPieceInfo()をコールして、フェッチ・バッファを指定します。
  6. 再度、 OCIStmtFetch2()をコールし、実際のピースを取り出します。OCIStmtFetch2()OCI_SUCCESSが戻された場合は、すべてのピースが正常にフェッチされています。OCIStmtFetch2()OCI_NEED_DATAが戻された場合は、ステップ4に戻って次のピースを処理します。その他の値が戻された場合は、エラーが起きています。

LOBのピース単位のバインドおよび定義

LOBのピース単位のバインドおよび定義を実行する方法について説明します。

次の2つがあります。

  • データ・インタフェースの使用

    次の関数の入力データ型として、SQLT_CHR (VARCHAR2)またはSQLT_LNG (LONG)を使用して、CLOB列の文字データをバインドまたは定義できます。次の関数の入力データ型として、SQLT_LBI (LONG RAW)およびSQLT_BIN (RAW)を使用して、BLOB列の行データをバインドまたは定義することもできます。

    • OCIDefineByPos()またはOCIDefineByPos2()

    • OCIBindByName()またはOCIBindByName2()

    • OCIBindByPos()またはOCIBindByPos2()

    次に説明するピース単位操作はすべて、この場合、CLOB列およびBLOB列に対してサポートされています。

  • LOBロケータの使用

    次の関数の入力データ型として、SQLT_CLOB (CLOB)またはSQLT_BLOB (BLOB)を使用して、CLOB列およびBLOB列のLOBロケータをバインドまたは定義できます。

    • OCIDefineByPos()またはOCIDefineByPos2()

    • OCIBindByName()またはOCIBindByName2()

    • OCIBindByPos()またはOCIBindByPos2()

    次に、OCILob*関数をコールし、データを読み取って操作する必要があります。OCILobRead2()およびOCILobWrite2()は、ピース単位モードおよびコールバック・モードをサポートしています。

関連項目: