ヘッダーをスキップ
Oracle Call Interfaceプログラマーズ・ガイド
11g リリース1(11.1)
E05677-02
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

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

この章は、次の項目で構成されています。

OCIでのバインドの概要

この章では、バインドと定義の基本概念をもう一度取り上げ、OCIアプリケーションで使用できる様々な種類のバインドと定義を詳しく説明します。さらに、構造体配列の使用方法、およびバインド、定義、文字変換における諸問題についても説明します。

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

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

次のように変数を宣言したとします。

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

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


関連項目:


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

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

図5-1の説明は次にあります
「図5-1 OCIBindByName()を使用したプレースホルダとプログラム変数の関連付け」の説明

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


注意:


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

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番目のプレースホルダは、最初のプレースホルダからのバインド情報を継承します。

OCI配列インタフェース

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

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

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

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


注意:


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

配列DML文で使用できる最大行数は、4GB -1です。


PL/SQLのプレースホルダのバインド

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にも変数をバインドします。


注意:


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


関連項目:


PL/SQLプレースホルダのバインドに関する詳細は、「名前付きデータ型およびREFバインドの情報」を参照してください。

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

プレースホルダは複数のステップでバインドされます。単純なスカラー・バインドまたは配列バインドの場合は、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));

注意:


checkerr()関数によって、OCIアプリケーションからのリターン・コードが評価されます。この関数に対するコードのリストは、「OCIプログラミング・ステップ」を参照してください。

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が未定義であることに注意してください。これは、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での拡張バインド操作

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

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

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

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

バインドのタイプ バインドのデータ型 注意

スカラー

任意のスカラー・データ型

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

スカラーの配列

任意のスカラー・データ型

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

名前付きデータ型

SQLT_NTY

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

  • OCIBindByName()またはOCIBindByPos()

  • OCIBindObject()

REF

SQLT_REF

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

  • OCIBindByName()またはOCIBindByPos()

  • OCIBindObject()

LOB

BFILE

SQLT_BLOB

SQLT_CLOB

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

構造体配列

または静的配列

可変

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

  • OCIBindByName()またはOCIBindByPos()

  • OCIBindArrayOfStruct()

ピース単位挿入

可変

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

REF CURSOR変数

SQLT_RSET

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



関連項目:


LOBのバインド

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

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

  • 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()を使用して、各配列要素を初期化する必要があります。BLOBCLOBおよびNCLOBを割り当てる場合は、OCI_DTYPE_LOBtypeパラメータとして使用します。BFILEを割り当てる場合は、OCI_DTYPE_FILEを使用します。

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

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

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


関連項目:


OCI LOB関数の詳細は、第7章「LOBおよびBFILEの操作」を参照してください。

LOBデータのバインド

0(ゼロ)以外のあらゆるサイズのLOBでINSERTUPDATEに対するバインドが可能です。OCIBindByPos()OCIBindByName()およびPL/SQLのバインドを使用すると、データをLOB列にバインドできます。

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

CREATE TABLESPACE ... AUTOEXTENT ON ... TEMPORARY ...;

LOBデータのバインドに対する制限

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

  • INSERT AS SELECT操作で、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 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'));
    

LOBデータのバインドの例

次の例で使用される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);
例1: LOBのバインド
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);
}
例2: LOBのバインド
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);
}
例3: LOBのバインド
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);
}
例4: LOBのバインド
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);
}
例5: LOBのバインド
void insert()
{
   /* Piecewise, callback and array insert/update operations similar to
    * the allowed regular insert/update operations are also allowed */
}
例6: LOBのバインド
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);
}
例7: LOBのバインド
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);
}
例8: LOBのバインド
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);
}

OCI_DATA_AT_EXECモードでのバインド

OCIBindByName()またはOCIBindByPos()コールのmodeパラメータがOCI_DATA_AT_EXECに設定されているとき、アプリケーションで実行時にデータを提供するコールバック方式を使用する場合は、追加のOCIBindDynamic()コールが必要です。OCIBindDynamic()のコールは、提供されたデータまたはその一部を示すために、必要に応じてコールバック・ルーチンを設定します。OCI_DATA_AT_EXECモードを選択した場合でも、コールバックのかわりに標準のOCIのピース単位ポーリング・メソッドを使用する場合は、OCIBindDynamic()コールは必要ありません。

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


関連項目:


ピース単位操作の詳細は、「OCIでのランタイム・データ割当てとピース単位操作」を参照してください。

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


関連項目:


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

基本的な定義は、定位置コールの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;
  }

関連項目:


記述ステップの詳細は、「選択リスト項目の記述」を参照してください。

拡張OCI定義

場合によっては、定義ステップではOCIDefineByPos()のコールのみでなく、他のコールも必要です。配列のフェッチ(OCIDefineArrayOfStruct())または名前付きデータ型のフェッチ(OCIDefineObject())の属性を定義する追加コールがあります。たとえば、1つの名前付きデータ型列を指定して複数の行をフェッチするには、列に対して3つのコールすべてを呼び出す必要がありますが、スカラー列の複数行をフェッチするには、OCIDefineArrayOfStruct()OCIDefineByPos()で十分です。

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

OCIでの拡張定義操作

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

場合によっては、定義ステップでは、配列のフェッチ(OCIDefineArrayOfStruct())または名前付きデータ型のフェッチ(OCIDefineObject())の属性を定義する追加コールが必要になります。たとえば、1つの名前付きデータ型列を指定して複数の行をフェッチするには、列に対して3つのコールすべてを呼び出す必要がありますが、スカラー列の複数行をフェッチするには、OCIDefineArrayOfStruct()OCIDefineByPos()で十分です。

LOB出力変数の定義

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

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

  • 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()を使用して、各配列要素を初期化する必要があります。BLOBCLOBおよびNCLOBを割り当てる場合は、OCI_DTYPE_LOBtypeパラメータとして使用します。BFILEを割り当てる場合は、OCI_DTYPE_FILEを使用します。

LOBデータの定義

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

次の例で使用されるSQL文について考えます。

CREATE TABLE lob_tab (C1 CLOB, C2 CLOB);

例1: 実行前の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);
}

例2: 実行後の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()をコールする必要があります。アプリケーションで、標準のポーリング・メカニズムではなくコールバックを使用する場合は、追加のOCIDefineDynamic()コールが必要です。

OCIでの構造体配列のバインドと定義

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

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

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

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

スキップ・パラメータ

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

図5-2は、スキップ・パラメータの決定方法を示しています。このケースでは、スキップ・パラメータは、フィールド1フィールド2およびフィールド3のサイズの合計です。それぞれのフィールドのサイズは、8バイトです。このサイズは、1つの構造体のサイズと同じです。

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

図5-2の説明は次にあります
「図5-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バイトの適切なスキップ・パラメータを戻すオペレーティング・システムもありますが、5バイトのskip_parameterを戻すシステムもあります。そのような場合、スキップ・パラメータの正しい値を得るには次の文を使用します。

skip_parameter = sizeof(demo_array[0]);

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

構造体配列は、単一変数の配列をバインドおよび定義する機能を拡張したものです。単一変数の配列操作を指定する場合、関連したスキップ・パラメータは、その配列のデータ型のサイズと等しくなります。たとえば、配列を次のように宣言したとします。

text emp_names[4][20];

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

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

構造体配列に関係する操作を行う際には、次の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にこの値を指定する場合は、パラメータvaluepOCIIOVのアドレスを渡す必要があります。データ型のサイズは、パラメータ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にこの値を指定する場合は、パラメータvaluepOCIIOVのアドレスを渡します。データ型のサイズは、パラメータ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でのRETURNING句を使用したDML

OCIでは、SQL INSERT文、UPDATE文およびDELETE文でのRETURNING句の使用をサポートします。この項では、RETURNING句を使用してDML文を正しく実装するためのルールについて、その概要を説明します。


関連項目:

  • 例全体は、Oracleのインストールに含まれているデモ・プログラムを参照してください。追加情報は、付録B「OCIデモ・プログラム」を参照してください。

  • INSERT文、UPDATE文またはDELETE文でのRETURNING句の使用の詳細は、『Oracle Database SQLリファレンス』を参照してください。


RETURNING句を使用したDMLの使用方法

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回の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句のバインド変数を操作するとき、次のルールに従う必要があります。

  1. OCIBindByName()またはOCIBindByPos()を使用して、OCI_DATA_AT_EXECモードでRETURNING句のプレースホルダをバインドした後で、各プレースホルダに対してOCIBindDynamic()をコールします。

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

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

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

RETURNING句を使用したDML文では、複製バインドは使用できません。また、DMLセクションのバインド変数と、その文のRETURNINGセクションのバインド変数を重複させることはできません。


注意:


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

OCIエラー処理

OCIBindDynamic()に提供されたOUTバインド関数には、エラー時に文の結果の一部を受け取る準備が必要です。10回実行されるDML文をアプリケーションで発行し、5回目の実行中にエラーが発生した場合、サーバーは、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つのステップが必要です。

  1. 初期バインド情報を、OCIBindByName()を使用して設定します。

  2. REF(TDOを含みます)に対する追加のバインド情報は、OCIBindObject()を使用して設定します。

  3. 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コールバックに関するその他の注意

コールバック関数がコールされるとき、バインド・ハンドルのOCI_ATTR_ROWS_RETURNED属性により、アプリケーションでは、ある特定の反復処理で戻される行数がわかります。最初のコールバックの反復処理中に、そのバインド変数に対して戻されるすべての行に空白を割り当てることができます。同一の反復処理内で後続のコールバックを実行しているときは、割り当てられた空白内の正確なメモリーまでバッファ・ポインタを増分させます。

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

OCIでは、各反復処理で複数の行を戻す単一行DML操作および配列DML操作に追加の機能性を提供します。この機能を利用するには、バインド・コールのOUTバッファを、OCIStmtExecute()コールで指定した反復件数以上の値に指定する必要があります。これは、コールバックを介してバインド・バッファに提供される追加機能です。

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

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

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

キャラクタ・セットの選択

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

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

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

キャラクタ・セット・フォームとキャラクタ・セット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変換は行われません。



関連項目:


NCHARデータの詳細は、『Oracle Databaseリファレンス』を参照してください。

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の各国語キャラクタ・セットを戻します。

次のコードは、これらの関数使用例です(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_MAXDATA_SIZE属性の使用

更新操作または挿入操作は、変数のバインドによって行われます。変数をバインドするときは、バインド・ハンドルのOCI_ATTR_MAXCHAR_SIZEおよび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(宛先列)

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

たとえば、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属性の値に関係なく、常にバイト数による長さです。また、送受信する実際の長さもバイト数になります。

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

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

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

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

挿入時のバッファの拡張

挿入時のバッファ拡張による予期しない動作の発生を回避する必要があります。

データベース列に文字長セマンティクスがあり、その列にユーザーが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での選択

動的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単位になります。


注意:


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

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

  • リリース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のみを指定した場合に類似しています。

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

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

文字数Nを指定して列が作成されると、次の表では、データベースでの実際の割当てが最大値で想定されます。割り当てられる実際のバイト数は、Nの倍数、つまりNM倍です。現在、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);
...

UTF-16のバインドと定義のコード例

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に準拠しているオペレーティング・システムでは、キャスト演算子を使用してutextwchar_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

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を文ハンドルにバインドします。

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 CURSORstm2pにフェッチすると、最初のREF CURSORからのデータにはアクセスできなくなります。

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

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


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

OCIを使用すると、データをピース単位で挿入、更新およびフェッチできます。 また、配列の挿入または更新の場合は、バインド値の静的配列のかわりに、OCIを使用して動的にデータを提供できます。非常に大きな列は、小さなサイズのチャンク(かたまり)が連続したものとして挿入または取出しができます。これによって、クライアント側のメモリー所要量を最小限にできます。

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

OCIのピース単位機能は、文字列やバイナリ・データの膨大なブロックで操作(CLOBBLOBLONGRAWLONG 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に設定した場合は、アプリケーションが、フェッチ時にデータを受け取るための割当てスペースを動的に提供することを示します。

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

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


注意:


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

実行時のINSERTまたはUPDATEデータの提供

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にこの手順を示しています。

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

図5-3の説明は次にあります
「図5-3 ピース単位挿入の実行」の説明

  1. OCI環境を初期化して必要なハンドルを割り当てます。サーバーに接続してユーザーを認証し、文の要求を準備します。

  2. OCIBindByName()またはOCIBindByPos()を使用して、プレースホルダをバインドします。使用するピースの実際のサイズを指定する必要はありません。実行時に提供できるデータの全体のサイズを指定します。

  3. OCIStmtExecute()の最初のコールを実行します。この時点ではデータは挿入されていません。アプリケーションにOCI_NEED_DATAエラー・コードが戻されます。その他の値が戻された場合は、エラーが起きたことを示しています。

  4. OCIStmtGetPieceInfo()をコールし、挿入する必要があるピースの情報を取り出します。OCIStmtGetPieceInfo()のパラメータには、必要なピースが最初のピース(OCI_FIRST_PIECE)なのか、後続のピース(OCI_NEXT_PIECE)なのかを示す値を戻すポインタが含まれています。

  5. アプリケーションは、挿入するデータのピースをバッファに移し、次のパラメータを使用してOCIStmtSetPieceInfo()をコールします。

    • ピースへのポインタ

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

    • 次のピースかどうかを示す値

    1. 最初のピース(OCI_FIRST_PIECE

    2. 中間のピース(OCI_NEXT_PIECE

    3. 最後のピース(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パラメータについて、適切なコールバックを指定します。

実行時のフェッチ情報の提供

modeパラメータにOCI_DYNAMIC_FETCHを設定してOCIDefineByPos()をコールすると、アプリケーションでは、フェッチ時のデータ・バッファに関する情報を指定できます。また、OCIDefineDynamic()をコールして、データ・バッファに関する情報を取得するために呼び出すコールバック関数を設定する必要がある場合があります。

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

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

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

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

フェッチ・バッファは、任意のサイズにすることができます。また、フェッチする各ピースは、同じサイズである必要はありません。ただし、最後にフェッチするサイズは、残っている最後のピースに一致する必要があります。フェッチする各ピースのサイズは、OCIStmtSetPieceInfo()コールでそれぞれ設定します。図5-4にこの手順を示しています。

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

図5-4の説明は次にあります
「図5-4 ピース単位フェッチの実行」の説明

  1. OCI環境を初期化して必要なハンドルを割り当てます。データベースに接続してユーザーを認証します。文を準備して実行します。

  2. modeOCI_DYNAMIC_FETCHに設定し、OCIDefineByPos()を使用して出力変数を定義します。この時点では、使用するピースの実際のサイズを指定する必要はありません。実行時にフェッチするデータの全体のサイズを指定します。

  3. OCIStmtFetch()の最初のコールを実行します。データは取り出されていません。アプリケーションにOCI_NEED_DATAエラー・コードが戻されます。その他の値が戻された場合は、エラーが起きています。

  4. OCIStmtGetPieceInfo()をコールし、フェッチするピースに関する情報を取得します。piecepパラメータは、そのピースが最初のピース(OCI_FIRST_PIECE)であるか、その後続のピース(OCI_NEXT_PIECE)であるか、または最後のピース(OCI_LAST_PIECE)であるかを示します。

  5. OCIStmtSetPieceInfo()をコールして、フェッチ・バッファを指定します。

  6. 再度、 OCIStmtFetch()をコールし、実際のピースを取り出します。OCIStmtFetch()OCI_SUCCESSが戻された場合は、すべてのピースが正常にフェッチされています。OCIStmtFetch()OCI_NEED_DATAが戻された場合は、ステップ4に戻って次のピースを処理します。その他の値が戻された場合は、エラーが起きています。

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

LOBのピース単位のバインドおよび定義を実行するには、次の2つの方法があります。

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

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

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

  2. LOBロケータの使用

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

    • OCIDefineByPos()

    • OCIBindByName()

    • OCIBindByPos()

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


関連項目: