ヘッダーをスキップ
Pro*C/C++プログラマーズ・ガイド
11gリリース2(11.2)
B61343-01
  ドキュメント・ライブラリへ
ライブラリ
製品リストへ
製品
目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

18 コレクション

この章では、コレクションと呼ばれる他の種類のオブジェクト型と、Pro*C/C++でのコレクションの使用方法を説明します。コレクションとその要素にアクセスする方法を示します。この章の項目は、次のとおりです。

コレクション

コレクション・オブジェクト・タイプには、NESTED TABLEおよびVARRAYの2種類があります。

コレクション型は、リレーショナル列に指定できるのみでなく、オブジェクト型の属性としても指定できます。すべてのコレクションは、データベース内では必ず名前付きのオブジェクト型である必要があります。VARRAYの場合は、最初にデータベース内に名前付きの型を作成して、希望する配列の要素型と最大配列サイズを指定する必要があります。

Oracleでは、ネスト・レベルを単一にするという制限がなくなっています。Oracleではオブジェクトの複数レベルのコレクションがサポートされるため、NESTED TABLEとVARRAYを複数レベルで使用できます。

NESTED TABLE

NESTED TABLEは、列内での要素と呼ばれる行の集まり(コレクション)です。データベース表の各行には、そのような要素が多数あります。簡単な例として、各従業員の作業リストがあります。この場合、多対1リレーションシップを1つの表に格納でき、従業員と作業の表を結合する必要はありません。

NESTED TABLEは、次の点でC言語の配列およびC++配列と異なります。

  • 配列には一定の上限がありますが、NESTED TABLEには制限がありません(インデックス最大値がありません)。

  • 配列には連続する添字があり、稠密です。NESTED TABLEは稠密または疎密です。NESTED TABLEがプログラムの配列に取り出されると、間隔はスキップされ、間隔のない稠密な配列が作成されます。

CREATE TYPE文を使用して表タイプを定義します。表タイプは、リレーショナル表の1つ以上の列でその他のオブジェクト型の中にネストできます。

たとえば、組織の各部門に複数のプロジェクトを格納する場合を考えます。

CREATE TYPE project_type AS OBJECT (
    pno            CHAR(5),
    pname          CHAR(20),
    budget         NUMBER(7,2)) ;

CREATE TYPE project_table AS TABLE OF project_type ;

CREATE TABLE depts (
    dno            CHAR(5),
    dname          CHAR(20),
    budgets_limit  NUMBER(15,2),
    projects       project_table)
    NESTED TABLE projects STORE AS depts_projects ;

VARRAY

VARRAY型を作成する場合、NESTED TABLEとは異なり、要素の最大数を指定する必要があります。NESTED TABLEでは稠密または疎密にできますが、VARRAYは疎密にはできません。VARRAYおよびNESTED TABLEの要素は、両方とも0から始まります。

次のようなCREATE TYPE文で、VARRAYを作成します。

CREATE TYPE employee AS OBJECT 
(
  name   VARCHAR2(10),
  salary NUMBER(8,2)
) ;
CREATE TYPE employees AS VARRAY(15) OF employee ;
CREATE TYPE department AS OBJECT
(
  name VARCHAR2(15),
  team employees
) ;

VARRAYは、リレーショナル表の列またはオブジェクト型の属性として使用します。この場合、各チームについて最大15レコード(チームの名前を含みます)で構成されるリレーショナル表と比較して、記憶域を節約できます。

C言語およびコレクション

C言語またはC++プログラムでは、NESTED TABLEはコレクションのインデックス値0から読込みが開始されます。配列にNESTED TABLEを書き込むと、NESTED TABLEの要素は配列インデックス0で格納されます。配列に格納されたNESTED TABLEが疎密(インデックスに間隔があります)の場合、間隔はスキップされます。配列がNESTED TABLEに読み込まれると、間隔が再作成されます。

C言語またはC++プログラムでは、VARRAYは配列に書き込まれます(インデックス0から開始します)。VARRAYに再び読み込まれると、要素はインデックス0から同じ順序でリストアされます。このため、配列を使用するとコレクションに簡単にアクセスできます。

コレクションの記述子

NESTED TABLEのC言語のデータ型は、OCITableへのポインタです。VARRAYの場合は、OCIArrayへのポインタになります。(両方ともOCICollへのポインタのサブタイプです。)OTT(Object Type Translator)ユーティリティを使用して、アプリケーション・コードにインクルードするヘッダー・ファイルにtypedefを生成します。第19章「Object Type Translator(OTT)」も参照してください。

コレクションのホスト構造体は記述子で、これを介してコレクションの要素にアクセスできます。この記述子には、実際のコレクション要素は保持されていませんが、その要素へのポインタが格納されています。記述子とその関連要素のためのメモリーは、オブジェクト・キャッシュで入手できます。

オブジェクト型における通常の手順に従って、OTT生成の型ファイルを、Pro*C/C++に対するINTYPEプリコンパイラ・オプション内と、#includeプリプロセッサ・ディレクティブを使用してPro*C/C++プログラムにインクルードされたOTT生成のヘッダー内に指定する必要があります。このスキーマにより、コレクション・オブジェクト・タイプに対する適切なタイプ・チェックがプリコンパイル中に間違いなく実行されます。

ただし、他のオブジェクト型とは異なり、コレクション・オブジェクト・タイプは、OTTによって生成される特別なインジケータ構造体を必要としません。かわりにスカラー・インジケータが使用されます。その理由は、アトミックNULLインジケータのみで十分にコレクション型全体がNULLかどうかがわかるからです。個々のコレクション要素のNULL状態は、(オプションで)各要素に対応する別個のインジケータで表すことができます。

ホスト変数と標識変数の宣言

コレクション・オブジェクト・タイプを表すホスト変数は、その他のオブジェクト型の場合と同様に、該当するOTT生成の型へのポインタとして宣言する必要があります。

ただし、コレクション・オブジェクト・タイプ全体を表す標識変数は、その他のオブジェクト型の場合とは異なり、2バイトの符号付きスカラー型OCIIndとして宣言されます。標識変数はオプションで設定しますが、Pro*C/C++で宣言された各ホスト変数に対してそれぞれ1つずつ指定するようにプログラミングすることをお薦めします。

コレクションの操作

コレクションを操作する方法は2つあります。コレクションを自律型エンティティとして扱い、コレクションの要素へのアクセスが発生しない場合と、コレクションの要素に対するアクセス、追加および切捨てなどが発生する場合です。

自律型コレクション・アクセス

Cコレクション記述子(OCITableまたはOCIArray)を使用した場合、コレクション全体を1つのエンティティとして割り当てること以外はできません。OBJECT GET埋込みSQL文を使用すると、コレクションがCホスト変数記述子にバインドされます。OBJECT SET文では、逆にCホスト記述子がコレクションにバインドされます。

1つの文中で複数のコレクションを1つの互換Cコレクション記述子にバインドしたり、コレクションをCコレクション記述子にバインドしている文中で他のスカラーにバインドすることもできます。

コレクション要素アクセス

Cコレクション記述子は、コレクションの要素にアクセスするために使用します。記述子には、始点およびエンドポイントなどのコレクションの内部属性およびその他の情報が含まれます。

要素のスライスは、互換データ型を持つホスト配列にバインドされます。コレクションのスライスとは、開始インデックスと終了インデックス間の内容のことです。スライスは配列にマップされます。配列の次元がスライス要素数よりも大きいことがあります。

スカラーのバインドは、ホスト配列の次元を1にすること、またはオプションのFOR句が1に評価されることと同じことです。

アクセスのルール

アクセスのルールは、自律型アクセスと要素アクセスで異なります。

自律型アクセス

  • コレクションは1つの全体として扱われるため、FOR句は使用できません。

  • NESTED TABLEとVARRAYの定義方法が異なるため、それらの間で代入を行うことはできません。

  • 1つの文中で、複数のコレクションを様々に組み合せてCコレクション記述子に代入することができます。1つの文中で、コレクションをCコレクション記述子に代入したり、他のスカラー・データ型をバインドしたりできます。

要素アクセス

  • FOR句を使用できます。省略すると、配列サイズの最小値が実行の繰返し回数になります。

  • 一度に複数のコレクションにアクセスすることはできません。


    注意:

    FOR句の変数では、処理する配列の要素数を指定します。この数は、最も小さい配列サイズを超過しないように設定します。この値は内部的には記号の付いていない数量として扱われます。付号付きのホスト変数を使用して負の値を渡すと、予期せぬ動作が発生します。

標識変数

アクセス方法ごとに標識変数の使用方法が異なります。

自律型バインド

1つの標識変数には、コレクションのNULL状態が1つのエンティティとして格納されます。各要素のNULL状態は格納されていません。

要素バインド

標識変数には、要素がNULLであるかどうかが格納されます。コレクション・データのスライスが、独自のインジケータ配列のホスト配列にバインドされている場合は、インジケータ配列には各要素のNULL状態がスライスに格納されます。

コレクション要素型がユーザー定義オブジェクト型の場合は、ホスト変数に対応付けられた標識変数に、オブジェクトおよびその属性のNULL状態が格納されます。

OBJECT GETおよびSET

OBJECT SETおよびOBJECT GETというナビゲーショナル文を使用すると、コレクション属性および定義したオブジェクト型の取出しおよび更新を行うことができます。

次の場合にオブジェクト型の要素に対してOBJECT GET文を実行すると、オブジェクトのすべての属性がホスト変数に取り出されます。

 '*' | {attr [, attr]}  FROM 

句が省略されるかOBJECT GET * FROMが使用される場合。

EXEC SQL [AT [:]database] 
   OBJECT GET [  '*' | {attr [,attr]}  FROM]
      :object [[INDICATOR] :object_ind]
         INTO {:hv [[INDICATOR] :hv_ind]
            [,:hv [[INDICATOR] :hv_ind]]} ;

次の場合にOBJECT SET文を実行すると、オブジェクトのすべての属性に対してホスト変数を使用した更新が行われます。

 '*' | {attr, [, attr]}  OF

句が省略されるかOBJECT SET * OFが使用される場合。

EXEC SQL [AT [:]database] 
   OBJECT SET [  '*' | {attr [, attr]} OF]
     :object [INDICATOR] :object_ind]
        TO {:hv [[INDICATOR] :hv_ind]
            [,:hv [[INDICATOR] :hv_ind]]} ;

関連項目

OBJECT GET文およびOBJECT SET文の詳細は、「オブジェクト属性とC言語のデータ型の変換」を参照してください。

この表は、これら2つの文でオブジェクトとコレクション型をどのようにマッピングするかを示しています。

表18-1 オブジェクト属性およびコレクション属性

属性型 記述 ホスト・データ型

オブジェクト

OTT生成の構造体

OTT構造体へのポインタ

コレクション

OCIArray、OCITable (OCIColl)

OCIArray *、OCITable * (OCIColl *)


オブジェクトまたはコレクションは、バインドされる属性と型互換性を持つ必要があります。コレクション属性の型互換性は両方がVARRAYまたはNESTED TABLEのいずれかで、その要素型に互換性がある場合にかぎり保たれます。

次の表は2つのコレクション型の要素でどのように互換性を保てるかを示しています。

表18-2 コレクションおよびホスト配列で可能な型変換

コレクション要素型 記述 ホスト・データ型

CHAR、VARCHAR、VARCHAR2

OCIString

STRING、VARCHAR、CHARZ、OCIString

REF

OCIRef

OCIRef

INT、INTEGER、SMALLINT

OCINumber

INT、SHORT、OCINumber

NUMBER、NUMERIC、REAL、FLOAT、DOUBLE PRECISION

OCINumber

INT、FLOAT、DOUBLE、OCINumber

DATE

OCIDate

STRING、VARCHAR、CHARZ、OCIDate


REFが参照するオブジェクトは、バインドされるREFと型互換性を持つ必要があります。

これらの表の場合、OBJECT GETでは「記述」列に指定された形式が使用され、左端のデータベース型から「ホスト・データ型」列の形式を使用する内部データ型に変換されます。OBJECT SETでは逆の変換が行われます。

明示的タイプ・チェックはサポートされません

プリコンパイラでは、コレクション要素データ型とホスト変数データ型間のバインド時の、明示的タイプ・チェックはサポートされません。タイプ・チェックは実行時に行われます。

COLLECTION文

この項では、COLLECTION文について説明します。

COLLECTION GET

用途

COLLECTION GET文は、OBJECT GET文に似ていますが、コレクションを対象としています。コレクション要素の取得、現行のスライスの設定、要素のC言語のデータ型への変換(必要な場合)を行います。

構文

EXEC SQL [AT [:]database] [FOR :num]
   COLLECTION GET :collect [[INDICATOR] :collect_ind]
      INTO :hv [[INDICATOR] :hv_ind] ;

変数

num (IN)

要求する要素の数。この句を省略すると、コレクションから取得する要素の数は、ホスト変数の配列サイズ(スカラーは1)により決定されます。

collect (IN)

ホスト変数Cコレクション記述子。

collect_ind (IN)

コレクションのNULL状態を戻すオプションの標識変数。

hv (OUT)

コレクション要素値を受け取るホスト変数。

hv_ind (OUT)

スカラーまたは配列にスライスの各要素の状態が格納されている場合、hvのNULL状態を戻すオプションの標識変数。

使用上の注意

最後のCOLLECTION GETから実際に戻される要素の数は、sqlca.sqlerrd[2]に設定されます(すべてのGET累計ではありません)。「sqlerrd」も参照してください。

スライスのエンドポイントのいずれかまたは両方がコレクションの境界を超過したときは、戻される要素数が要求した数よりも少ないことがあります。次の場合にこの状態が発生します。

  • コレクション記述子が、正しい構文のALLOCATE文で初期化されなかった場合、NULLの場合、その他の理由で無効な場合。

  • コレクションがNULLの場合。対応付けられるインジケータは-1になります。

  • コレクションが空(要素がない)の場合。

  • コレクションに残っている要素数を超える要素が要求された場合。

  • COLLECTION TRIM文が実行された結果、現行のスライスで終了インデックスが開始インデックスよりも前になっている場合。

Cコレクション記述子が適切に初期化されなかった場合は、エラーになります。前述のリスト以外の場合は、「ORA-01403: データが見つかりません。」というエラーが発生します。この場合、エラー発生前に正常に取得できた要素の合計数は、sqlca.sqlerrd[2]に格納されたままです。

最初のGETまたはRESET後の最初のGETにより、スライスは次のようになります。

  • スライスの終了インデックスは、検出された最後の要素のインデックスになります。要求する要素数によって変わります。コレクションに要求を満たせるだけの要素が残っていない場合、最後のインデックスはコレクションの最後のインデックスになります。

引き続きGETを実行すると、スライスのインデックスは次のようになります。

  • 直前のスライスのエンドポイントの後に最初に検出された要素のインデックスが、開始インデックスになります。直前のスライスのエンドポイントの後に要素が残っていない場合、開始インデックスはコレクションの最後の要素のインデックスになります。

  • 次のスライスの終了インデックスは、検出された最後の要素のインデックスになります。要求した要素数によって変わります。直前のスライスで指定された位置で、要求を満たせるだけの要素がコレクションに残っていない場合、終了インデックスはコレクションの最後のインデックスになります。

COLLECTION SET

用途

COLLECTION SET文は、OBJECT SET文に似ています。コレクションの要素値の更新に使用します。現行のスライスの要素は、C固有型からOracleデータ型に変換されます。

構文

EXEC SQL [AT [:]database] [FOR :num]
   COLLECTION SET :collect [[INDICATOR] :collect_ind]
      TO :hv [[INDICATOR] :hv_ind] ;

変数

num (IN)

このオプションのスカラー値は、スライス内で更新される要素の最大数です。この句を省略すると、コレクションから更新される要素数は、ホスト変数の配列サイズ(スカラーは1)により決定されます。

collect (OUT)

ホスト変数Cコレクション記述子。

collect_ind (OUT)

コレクションのNULL状態を決定するオプションの標識変数。

hv (IN)

コレクション内で更新される値を含むホスト変数。

hv_ind (IN)

ホスト変数のNULL状態を表す、対応付けられた標識変数。

使用上の注意

次の制限が適用されます。

  • COLLECTION GETは、COLLECTION SETの前に実行する必要があります。

  • スライスの開始インデックスおよび終了インデックスは、常に変わりません。コレクションに格納されている要素数が、現行のスライスに格納できる要素数より少ない場合も変わりません。SET文では、スライスのエンドポイントは変更されません。現行のスライスの要素のみが変更されます。

  • COLLECTION SETでは、現行のスライスの要素のみが更新されます。COLLECTION SET文を使用して、新しい要素をコレクションに追加することはできません。

  • 現行のスライスに含まれる要素の数を超える要素をSET文で更新すると、既存のスライスに格納されている要素のみが更新されます。スライスのエンドポイント外の残りの要素は更新されず、ホスト変数で指定されたその他の値は使用されません。

オプションのFOR句で指定したホスト変数または値numの次元により、コレクションで更新を要求できる要素の最大数が決まります。

変数sqlca.sqlerrd[2]により、直前のSET文で正常に更新された要素の数が戻されます(累計ではありません)。次のような場合は、GET文と同様に、設定要求数よりも少ないことがあります。

  • Cコレクション記述子が、構文の正しいALLOCATE文で正常に初期化されなかった場合、NULLの場合、またはその他の理由で無効の場合。

  • コレクションが空の場合。

  • コレクションの現行のスライスの位置で、コレクションの残りの部分の要素数が、設定要求数よりも少なかった場合。

  • 現行のスライスのエンドポイントを超えた場合。既存のスライスの要素数以上の要素を設定すると、この状態になります。

  • コレクションに対してTRIMが実行され、コレクションの終了インデックスの最大値が現行のスライスの開始インデックスを下回った場合。

COLLECTION GETまたはSETの直後にCOLLECTION SETを実行した場合は、既存のスライスの要素の値のみが更新されます。COLLECTION SETの直後にCOLLECTION GETを実行すると、すでに説明したように次のスライスに移ります。

COLLECTION RESET

用途

コレクション・スライスのエンドポイントをコレクションの最初にリセットします。

構文

EXEC SQL [AT [:]database]
   COLLECTION RESET :collect 
      [ [INDICATOR] :collect_ind] ;

変数

collect (IN/OUT)

エンドポイントをリセットするコレクション。

collect_ind

コレクションのNULL状態を決定するオプションの標識変数。

使用上の注意

指定したコレクションがNULLまたは無効の場合は、エラーが発生します。

COLLETION RESETはコレクションのサイズまたは内容には影響しません。

COLLECTION APPEND

用途

コレクションの最後に要素のセット(1つ以上)を追加します。コレクションのサイズが増加します。

構文

EXEC SQL [AT [:]database] [FOR :num]
   COLLECTION APPEND :src [[INDICATOR] :src_ind]
      TO :collect [[INDICATOR] :collect_ind] ;

変数

num (IN)

追加する要素数が格納されたスカラー。指定しない場合は、配列サイズsrcが追加する要素数になります。

src (IN)

コレクションに追加する要素のスカラーまたは配列。

src_ind (IN)

追加する要素のNULL状態を決定するオプションの標識変数(スカラーまたは配列)。

collect (IN OUT)

要素を追加するコレクション。

collect_ind (IN)

コレクションのNULL状態を決定するオプションの標識変数。

使用上の注意

要素は一度に1つずつ追加されます(コレクションのサイズは1ずつ増加し、データがその要素にコピーされます)。

変数sqlca.sqlerrd[2]により、最後のAPPENDで正常に追加された要素数が戻されます(累計ではありません)。コレクションの上限を超えて要素を追加したり、NULLコレクションを追加したりすると、エラーが発生します。上限以内の要素のみが追加されます。

COLLECTION TRIM

用途

コレクションの最後から要素を削除します。

構文

EXEC SQL [AT [:]database]
   COLLECTION TRIM :num
      FROM :collect [[INDICATOR] :collect_ind] ;

変数

num (IN)

削除する要素数を示すホスト・スカラー変数。最大許容値は2GBです。

collect (IN OUT)

切り捨てるコレクション。

collect_ind (IN)

コレクションのNULL状態を決定するオプションの標識変数。

使用上の注意

次の制限が適用されます。

  • FOR句は使用できません。

  • numの最大値は2GBです(4バイト符号付きバイナリ変数の最大数)。

  • numにインジケータを使用することはできません。

numがコレクションのサイズよりも大きい場合、エラーが戻されます。TRIM文で現行のスライスから要素を削除すると、警告が戻されます。

COLLECTION DESCRIBE

用途

コレクションについての情報が戻されます。

構文

EXEC SQL [AT [:]database] 
   COLLECTION DESCRIBE :collect [[INDICATOR] :collect_ind]
      GET attribute1 [{, attributeN}]
         INTO :hv1 [[INDICATOR] :hv_ind1] [{, hvN [[INDICATOR] :hv_indN]}] ;

attributeNは次のとおりです。

DATA_SIZE | TYPECODE | DATA_TYPE | NUM_ELEMENTS
   | PRECISION | SCALE | TYPE_NAME | TYPE_SCHEMA | SIZE | TABLE_SIZE

変数

collect (IN)

ホスト変数Cコレクション記述子。

collect_ind (IN)

コレクションのNULL状態を含むオプションの標識変数。

hv1 .. hvN (OUT)

情報が格納される出力ホスト変数。

hv_ind1 .. hv_indN (OUT)

出力ホスト変数の標識変数。

使用上の注意

次の制限が適用されます。

  • コレクションをNULLにはできません。

  • ホスト変数型は、戻される属性の型と互換性を持つ必要があります。

  • 属性の標識変数は、テキストの切捨てが行われるTYPE_NAMEおよびTYPE_SCHEMA属性値でのみ必要です。

  • FOR句は使用できません。

  • 変数sqlca.sqlerrd[2]では、正常に取り出された属性の数を戻します。DESCRIBE文でエラーが発生した場合、sqlca.sqlqerrd[2]には、エラー発生前に戻された属性の数が格納されます。エラー発生時の属性数より1つ少なくなっています。

次の表は、属性、説明、および取り出される属性のC言語のデータ型を示しています。

表18-3 COLLECTION DESCRIBEの属性

属性 説明 C言語のデータ型 注意

DATA_SIZE

型属性の最大サイズ。戻される長さは、文字列のバイト長です。NUMBERの場合は22です。

unsigned short

オブジェクトまたはオブジェクトREF要素では無効です。

TYPECODE

OCI型コード。

OCITypeCode

-

DATA_TYPE

コレクション項目の内部数値型コード。

unsigned short

-

NUM_ELEMENTS

VARRAYの最大要素数。

unsigned int

VARRAY型にのみ有効です。

PRECISION

数値型属性の精度。戻される値が0の場合、記述される項目は初期化されず、データ・ディクショナリはNULLになります。

unsigned char

NUMBER型の要素にのみ有効です。

SCALE

数値型属性の位取り。戻される値が-127の場合、記述される項目は初期化されず、データ・ディクショナリはNULLになります。

signed char

NUMBER型の要素にのみ有効です。

TYPE_NAME

型の名前を含む文字列。オブジェクト型の場合、その名前が戻されます。REFの場合、REFによって参照されるデータ型の名前が戻されます。使用できる外部データ型はCHARZ、STRINGおよびVARCHARです。

char*

オブジェクトおよびオブジェクトREF要素にのみ有効です。

TYPE_SCHEMA

型を作成するスキーマ名。使用できる外部データ型はCHARZ、STRINGおよびVARCHARです。

char*

オブジェクトおよびオブジェクトREF要素にのみ有効です。

SIZE

コレクションに実際に格納されている要素数。NESTED TABLEの場合、SIZEにより空の要素が格納されます。TRIM文を実行すると、切り捨てられた要素数のみコレクションのSIZEが減少します。

signed int

-

TABLE_SIZE

NESTED TABLEの要素の数。間隔は含みません。

signed int

NESTED TABLEにのみ有効です。


表について

Pro*C/C++の外部データ型では、属性TYPE_NAMEおよびTYPE_SCHEMAのCHARZ、STRINGおよびVARCHARのみがサポートされます。

SIZEおよびTABLE_SIZEを除き、DESCRIBE属性は、すべてコレクションの要素型に依存し、コレクションの特定のインスタンスには依存しません。一方、SIZEおよびTABLE_SIZE属性は、値がコレクションの特定のインスタンスに完全に依存しています。割り当てられたコレクション記述子が再利用され、同じコレクションの異なるインスタンスが参照される場合、SIZEまたはTABLE_SIZEの値はコレクションのインスタンスごとに変わります。NUM_ELEMENTSはコレクション型(この例ではVARRAY)の属性で、コレクション要素型ではありません。また、コレクションの特定のインスタンスには依存しません。

コレクションを使用する場合の規則

  • ホスト変数コレクション記述子は、常に明示的に割り当てる必要があります。

  • メタデータ(コレクションおよびその要素型についての、データベースの内部Oracleデータ)は、ALLOCATEの実行中に収集されます。ALLOCATEが実行された接続がクローズしているとき、またはALLOCATEの実行後に型が変更されたときは、メタデータは無効になります。

  • 各Cコレクション記述子の使用を開始または終了するには、ALLOCATEまたはFREE文を使用します。

コレクション・サンプル・コード

COLLECTION SQL文を使用するSQLおよびPro*C/C++コードの例を示します。

型および表の作成

scott/tigerで接続し、SQLを使用して次の型を作成するとします。

CREATE TYPE employee AS OBJECT 
(
  name   VARCHAR2(10),
  salary NUMBER(8,2)
) ;
CREATE TYPE employees AS VARRAY(15) OF employee ;
CREATE TYPE department AS OBJECT
(
  name VARCHAR2(15),
  team employees
) ;

Object Type Translator(OTT)によって、ヘッダー・ファイルが生成されます。OTTの入力として、次の入力ファイル(ファイル名in.typ)が使用されます。

case=lower
type employee
type employees
type department

次のコマンドによりヘッダー・ファイルが生成されます。

ott intype=in.typ outtype=out.typ hfile=example.h user=scott/tiger code=c

このヘッダー・ファイルexample.hは、OTTにより生成されます。

#ifndef EXAMPLE_ORACLE
# define EXAMPLE_ORACLE

#ifndef OCI_ORACLE
# include <oci.h>
#endif

typedef OCIRef employee_ref ;
typedef OCIArray employees ;
typedef OCIRef department_ref ;

struct employee
{
   OCIString * name ;
   OCINumber salary ;
} ;
typedef struct employee employee ;

struct employee_ind
{
   OCIInd _atomic ;
   OCIInd name ;
   OCIInd salary ;
} ;
typedef struct employee_ind employee_ind ;
struct department_ind
{
   OCIInd _atomic ;
   OCIInd name ;
   OCIInd team ;
} ;
typedef struct department_ind department_ind ;

#endif

注意:

oci.hには、OCIArrayを定義したtypedefを持つorl.hが格納されています。typedefは、次の「typedef OCIColl OCIArray;」のようになります。OCICollは、汎用コレクションを表す不透明な構造体です。

次の1列が含まれる簡単な表を作成します。

CREATE TABLE division ( subdivision department ) ;

この表に複数の行を挿入します。

INSERT INTO division (subdivision) VALUES
(department('Accounting',
            employees(employee('John', 75000),
                      employee('Jane', 75000)))
);
INSERT INTO division (subdivision) VALUES
(department('Development',
            employees(employee('Peter', 80000),
                      employee('Paula', 80000)))
) ;
INSERT INTO division (subdivision) VALUES
(department('Research',
            employees(employee('Albert', 95000),
                      employee('Alison', 95000)))
);

これらの型定義および表の情報を、次の例で使用します。

GETおよびSETの例

この例では、オブジェクトのコレクション属性から値を取り出し、簡単な修正を加え、コレクションに戻します。

まず、example.hをインクルードし、オブジェクト型の変数を宣言する必要があります。

#include <example.h>
department *dept_p ;

「開発」部門を部署表から選択します。

       EXEC SQL ALLOCATE :dept_p ;
       EXEC SQL SELECT subdivision INTO :dept_p
          FROM division WHERE name = 'Development' ;

employeeオブジェクト型のチームVARRAYの変数および単一のemployeeオブジェクトを表す変数が必要です。開発部門のすべてのメンバーの給料を昇給します。そのための変数が必要です。

       employees *emp_array ;
       employee *emp_p ;
       double salary ;

作成したVARRAY Cコレクションおよびemployeeオブジェクト記述子に、ALLOCATEを実行する必要があります。ナビゲーショナル・アクセス用インタフェースを使用して、オブジェクトから実際のコレクションを取り出します。

       EXEC SQL ALLOCATE :emp_array ;
       EXEC SQL ALLOCATE :emp_p ;
       EXEC SQL OBJECT GET team FROM :dept_p INTO :emp_array ;

ループを使用し、VARRAY要素に対して処理を繰り返します。WHENEVERディレクティブを使用してループの終了を制御します。

       EXEC SQL WHENEVER NOT FOUND DO break ;
       while (TRUE)
         {

まず、コレクションから変更する要素を取り出します。実際の要素型は、employeeオブジェクトです。

            EXEC SQL COLLECTION GET :emp_array INTO :emp_p ;

実際のオブジェクト要素を取り出したため、既存のナビゲーショナル・アクセス用インタフェースを使用して属性の値を変更します。この例では、全員の給料を10%増やします。

            EXEC SQL OBJECT GET salary FROM :emp_p INTO :salary ;
            salary += (salary * .10) ;
            EXEC SQL OBJECT SET salary OF :emp_p TO :salary ;

変更が終わると、コレクションに現在含まれているオブジェクト要素の属性の値を更新できます。

            EXEC SQL COLLECTION SET :emp_array TO :emp_p ;
         }

すべてのコレクション要素に対して処理を繰り返した後に、そのコレクションを含むオブジェクトが格納されている表の列を更新する必要があります。

     EXEC SQL UPDATE division SET subdivision = :dept_p ;

次に、FREEを実行してすべてのリソースを解放し、COMMITを実行してこの一連の操作を終了します。

     EXEC SQL FREE :emp_array ;
     EXEC SQL FREE :emp_p ;
     EXEC SQL FREE :dept_p ;
     EXEC SQL COMMIT WORK ;

簡単な例ですが、必要な処理はすべて含まれています。ナビゲーショナルOBJECT GET文を応用して、コレクション属性をオブジェクトから取り出し、Cコレクション記述子に格納する方法は、明確に説明されています。さらに、そのCコレクション記述子を使用して、実際のコレクションの要素を取出しおよび更新を行うための、新しいCOLLECTION GET文およびSET文の使用方法について説明しました。コレクション・オブジェクト要素型の属性値の変更には、ナビゲーショナル・アクセス用インタフェースを使用しています。

DESCRIBEの例

この例では、DESCRIBE SQL文の使用方法を示します。任意のコレクションについての基本情報を検索します。

まず、例で使用されているヘッダー・ファイル、オブジェクト・ポインタおよびSQLコレクション記述子が必要です。

#include <example.h>
department *dept_p ;

前と同様に、オブジェクト・ポインタのALLOCATEを実行し、表からオブジェクトを取り出します。

EXEC SQL ALLOCATE :dept_p ;
EXEC SQL SELECT subdivision INTO :dept_p
   FROM division WHERE name = 'Research' ;

検索するコレクション属性情報を格納するPro*C/C++変数を宣言します。

int size ;
char type_name[9] ;
employees *emp_array ;

コレクション記述子を割り当て、ナビゲーショナル・アクセス用インタフェースを使用して、オブジェクトからコレクション属性を取り出します。

EXEC SQL ALLOCATE :emp_array ;
EXEC SQL OBJECT GET team FROM :dept_p INTO :emp_array ;

最後に、新しいCOLLECTION DESCRIBE文を使用して目的のコレクション属性情報を抽出します。

EXEC SQL COLLECTION DESCRIBE :emp_array
   GET SIZE, TYPE_NAME INTO :size, :type_name ;

注意:

この例のように、目的のコレクション属性名と同じホスト変数名を使用できます。

employeesは、オブジェクトemployeeのVARRAYのため、型名を抽出できます。

DESCRIBEが正常に実行されると、SIZEの値は2(このコレクション・インスタンスResearchの場合、2つの要素、AlbertおよびAlisonが存在します)になります。type_name変数は「EMPLOYEE\0」(デフォルトではCHARZ)になります。

SQL記述子およびオブジェクト・ポインタを使用した処理が終了した後に、FREEを実行してリソースを解放します。

EXEC SQL FREE :emp_array ;
EXEC SQL FREE :dept_p ;

この例では、Cコレクション記述子の参照先の基礎となるコレクションについて、記述子から情報を抽出するために使用するDESCRIBEのメカニズムについて説明しました。

RESETの例

開発の従業員の給料を昇給するかわりに、GETおよびSETの例のように、部署全体の給料を昇給します。

前の例と同様に、Object Type Translator(OTT)で生成されたサンプル・ヘッダー・ファイルなどを処理します。ただし、今回は、カーソルを使用して部署の部門ごとに、一度に1部門ずつ繰り返し実行します。

#include <example.h>
EXEC SQL DECLARE c CURSOR FOR SELECT subdivision FROM division ;

データを操作するローカル変数が必要になります。

department *dept_p ;
employees *emp_array ;
employee *emp_p ;
double salary ;
int size ;

オブジェクト変数およびコレクション変数を使用する前に、次のALLOCATE文を使用して初期化する必要があります。

EXEC SQL ALLOCATE :emp_array ;
EXEC SQL ALLOCATE :emp_p ;

カーソルを使用して、部署のすべての部門に対して繰り返し処理を行うことができるようになりました。

EXEC SQL OPEN c ;
EXEC SQL WHENEVER NOT FOUND DO break ;
while (TRUE)
    {
         EXEC SQL FETCH c INTO :dept_p ;

ここで、部門オブジェクトを使用します。ナビゲーショナル・アクセス用インタフェースを使用して、部門からVARRAY属性teamを抽出する必要があります。

         EXEC SQL OBJECT GET team FROM :dept_p INTO :emp_array ;

コレクションへの参照を開始する前に、RESET文を使用して、スライスのエンドポイントが現行のコレクション・インスタンスの始点(前のインスタンスの最後ではありません)に設定されていることを確認してください。

         EXEC SQL COLLECTION RESET :emp_array ;

VARRAYのすべての要素を繰り返し処理し、前と同様に給料を更新します。このループの場合も、既存のWHENEVERディレクティブは引き続き有効です。

        while (TRUE)
           {
            EXEC SQL COLLECTION GET :emp_array INTO :emp_p ;
            EXEC SQL OBJECT GET salary FROM :emp_p INTO :salary ;
            salary += (salary * .05) ;
            EXEC SQL OBJECT SET salary OF :emp_p TO :salary ;

処理が完了すると、コレクション属性を更新します。

            EXEC SQL COLLECTION SET :emp_array TO :emp_p ;
           }

前の例と同様に、変更が完了したコレクションを含むオブジェクトが格納された表の列を更新する必要があります。

        EXEC SQL UPDATE division SET subdivision = :dept_p ;
    }

ループが終了すると、処理は終了です。FREEを実行してすべてのリソースを解放し、COMMITで作業内容を送信します。

EXEC SQL CLOSE c ;
EXEC SQL FREE :emp_p ;
EXEC SQL FREE :emp_array ;
EXEC SQL FREE :dept_p ;
EXEC SQL COMMIT WORK ;

この例では、同じコレクション型の異なるインスタンスに対して、ALLOCATEで割り当てられたコレクション記述子の再利用方法について説明しています。COLLECTION RESET文により、スライスのエンドポイントが必ず現在のコレクション・インスタンスの最初にリセットされます。エンドポイントは、前のコレクション・インスタンスの参照中に移動された後は、それまでの位置に残りません。

COLLECTION RESET文をこのように使用すると、アプリケーション開発者は同じコレクション型の新しいインスタンスを作成するたびに、コレクション記述子に対して明示的にFREEおよびALLOCATEを実行する必要がなくなります。

サンプル・プログラム: coldemo1.pc

次のプログラムcoldemo1.pcは、demoディレクトリにあります。

この例では、Pro*Cクライアントからコレクション型データベース列を操作する3種類の方法について説明しています。この例では、NESTED TABLEを使用していますが、VARRAYにも適用できます。

この例では、SQL*Plusファイル、coldemo1.sqlを使用して、挿入データおよびcalidata.sqlに格納されているデータを使用する表をセットアップします。

REM ************************************************************************
REM ** This is a SQL*Plus script to demonstrate collection manipulation
REM ** in Pro*C/C++.
REM ** Run this script before executing OTT for the coldemo1.pc program
REM ************************************************************************

connect scott/tiger;

set serveroutput on;

REM Make sure database has no old version of the table and types

DROP TABLE county_tbl;
DROP TYPE citytbl_t;
DROP TYPE city_t;

REM ABSTRACTION:
REM The counties table contains census information about each of the
REM counties in a particular U.S. state (California is used here).  
REM Each county has a name, and a collection of cities.  
REM Each city has a name and a population.

REM IMPLEMENTATION:
REM Our implementation follows this abstraction
REM Each city is implemented as a "city" OBJECT, and the
REM collection of cities in the county is implemented using 
REM a NESTED TABLE of "city" OBJECTS.

CREATE TYPE city_t AS OBJECT (name CHAR(30), population NUMBER);
/

CREATE TYPE citytbl_t AS TABLE OF city_t;
/

CREATE TABLE county_tbl (name CHAR(30), cities citytbl_t)
  NESTED TABLE cities STORE AS citytbl_t_tbl;

REM Load the counties table with data.  This example uses estimates of
REM California demographics from Janurary 1, 1996.

@calidata.sql;

REM Commit to save
COMMIT;

表の設定方法およびこのプログラムでデモンストレーションする機能の説明については、次のプログラムの最初のコメントを参照してください。

            /* ***************************************** */
            /*   Demo program for Collections in Pro*C   */
            /* ***************************************** */

/*****************************************************************************
 
   In SQL*Plus, run the SQL script coldemo1.sql to create:
     - 2 types: city_t (OBJECT) and citytbl_t (NESTED TABLE)
     - 1 relational table county_tbl which contains a citytbl_t nested table
  
   Next, run the Object Type Translator (OTT) to generate typedefs of C structs
   corresponding to the city_t and citytbl_t types in the databases:
     ott int=coldemo1.typ outt=out.typ hfile=coldemo1.h code=c user=scott/tiger
  
   Then, run the Pro*C/C++ Precompiler as follows:
     proc coldemo1 intype=out.typ
  
   Finally, link the generated code using the Pro*C Makefile:
     (Compiling and Linking applications is a platform dependent step).
  
 ****************************************************************************
 
   Scenario: 
     We consider the example of a database used to store census 
     information for the state of California.  The database has a table
     representing the counties of California.  Each county has a name 
     and a collection of cities.  Each city has a name and a population.

   Application Overview:
     This example demonstrates three ways for the Pro*C client to 
     navigate through collection-typed database columns.  Although the
     examples presented use nested tables, they also apply to varrays.
     Collections-specific functionality is demonstrated in three
     different functions, as described in the following section.
        
     PrintCounties shows examples of 
     * Declaring collection-typed host variables and arrays
     * Allocating and freeing collection-typed host variables
     * Using SQL to load a collection-typed host variable
     * Using indicators for collection-typed host variables
     * Using OCI to examine a collection-typed host variables

     PrintCounty shows examples of 
     * Binding a ref cursor host variable to a nested table column
     * Allocating and freeing a ref cursor
     * Using the SQL "CURSOR" clause

     CountyPopulation shows examples of 
     * Binding a "DECLARED" cursor to a nested table column
     * Using the SQL "THE" clause

****************************************************************************/

/* Include files */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlca.h>                                /* SQL Communications Area */
#include <coldemo1.h>        /* OTT-generated header with C typedefs for the */
                                      /* database types city_t and citytbl_t */
#ifndef EXIT_SUCCESS
# define EXIT_SUCCESS   0
#endif
#ifndef EXIT_FAILURE
# define EXIT_FAILURE   1
#endif 

#define CITY_NAME_LEN    30
#define COUNTY_NAME_LEN  30
#define MAX_COUNTIES     60

/* Function prototypes */

#if defined(__STDC__)
 void OptionLoop( void );
 boolean GetCountyName( char *countyName );
 void PrintCounties( void );
 long CountyPopulation( CONST char *countyName );
 void PrintCounty( CONST char *countyName );
 void PrintSQLError( void );
 void PrintCountyHeader( CONST char *county );
 void PrintCity( city_t *city );
#else
 void OptionLoop();
 boolean GetCountyName(/*_ char *countyName _*/);
 void PrintCounties();
 long CountyPopulation(/*_ CONST char *countyName _*/);
 void PrintCounty(/*_ CONST char *countyName _*/);
 void PrintSQLError(/*_ void _*/);
 void PrintCountyHeader(/*_ CONST char *county _*/);
 void PrintCity(/*_ city_t *city _*/);
#endif

/* 
 * NAME
 *   main
 * COLLECTION FEATURES
 *   none
 */
int main()
{
  char * uid = "scott/tiger";
  
  EXEC SQL WHENEVER SQLERROR DO PrintSQLError();

  printf("\nPro*Census: Release California - Jan 1 1996.\n");
  EXEC SQL CONNECT :uid;
  
  OptionLoop();

  printf("\nGoodbye\n\n");
  EXEC SQL ROLLBACK RELEASE;
  return(EXIT_SUCCESS);
}

/* 
 * NAME
 *   OptionLoop
 * DESCRIPTION
 *   A command dispatch routine.
 * COLLECTION FEATURES
 *   none
 */
void OptionLoop()
{
  char choice[30];
  boolean done = FALSE;
  char countyName[COUNTY_NAME_LEN + 1];

  while (!done)
  {
    printf("\nPro*Census options:\n");
    printf("\tlist information for (A)ll counties\n");
    printf("\tlist information for one (C)ounty\n");
    printf("\tlist (P)opulation total for one county\n");
    printf("\t(Q)uit\n");
    printf("Choice? ");

    fgets(choice, 30, stdin);
    switch(toupper(choice[0]))
    {
    case 'A':
      PrintCounties();
      break;
    case 'C':
      if (GetCountyName(countyName)) 
        PrintCounty(countyName);
      break;
    case 'P':
      if (GetCountyName(countyName)) 
        printf("\nPopulation for %s county: %ld\n", 
               countyName, CountyPopulation(countyName));
      break;
    case 'Q':
      done = TRUE;
      break;
    default:
      break;
    }
  }
}


/* 
 * NAME
 *   GetCountyName
 * DESCRIPTION
 *   Fills the passed buffer with a client-supplied county name.
 *   Returns TRUE if the county is in the database, and FALSE otherwise.
 * COLLECTION FEATURES
 *   none
 */
boolean GetCountyName(countyName)
  char *countyName;
{
  int   count;
  int   i;

  printf("County name? ");
  fgets(countyName, COUNTY_NAME_LEN + 1, stdin);

  /* Convert the name to uppercase and remove the trailing '\n' */
  for (i = 0; countyName[i] != '\0'; i++)
    {
      countyName[i] = (char)toupper(countyName[i]);
      if (countyName[i] == '\n') countyName[i] = '\0';
    }

  EXEC SQL SELECT COUNT(*) INTO :count 
    FROM county_tbl WHERE name = :countyName;

  if (count != 1)
    {
      printf("\nUnable to find %s county.\n", countyName);
      return FALSE;
    }
  else
    return TRUE;
}


/* 
 * NAME
 *   PrintCounties
 * DESCRIPTION
 *   Prints the population and name of each city of every county 
 *   in the database.
 * COLLECTION FEATURES
 *   The following features correspond to the inline commented numbers
 *   1) Host variables for collection-typed objects are declared using 
 *      OTT-generated types.  Both array and scalar declarations are allowed.
 *      Scalar declarations must be of type pointer-to-collection-type, and
 *      array declarations must of type array-of-pointer-to-collection-type.
 *   2) SQL ALLOCATE should be used to allocate space for the collection.
 *      SQL FREE should be used to free the memory once the collection is
 *      no longer needed.  The host variable being allocated or free'd 
 *      can be either array or scalar.
 *   3) SQL is used to load into or store from collection-typed host variables
 *      and arrays.  No special syntax is needed.
 *   4) The type of an indicator variable for a collection is OCIInd.
 *      An indicators for a collections is declared and used just like
 *      an indicator for an int or string.
 *   5) The COLLECTION GET Interface is used to access and manipulate the
 *      contents of collection-typed host variables.  Each member of the
 *      collection used here has type city_t, as generated by OTT.
 */
void PrintCounties()
{
  citytbl_t *cityTable[MAX_COUNTIES];                                 /* 1 */
  OCIInd     cityInd[MAX_COUNTIES];                                   /* 4 */
  char       county[MAX_COUNTIES][COUNTY_NAME_LEN + 1];
  int        i, numCounties;
  city_t    *city;

  EXEC SQL ALLOCATE :cityTable;                                       /* 2 */
  EXEC SQL ALLOCATE :city;
  
  EXEC SQL SELECT name, cities
    INTO :county, :cityTable:cityInd FROM county_tbl;              /* 3, 4 */

  numCounties = sqlca.sqlerrd[2];

  for (i = 0; i < numCounties; i++) 
  {
    if (cityInd[i] == OCI_IND_NULL)                                   /* 4 */
      {
        printf("Unexpected NULL city table for %s county\n", county[i]);
      }
    else
      {                                                               /* 5 */
        PrintCountyHeader(county[i]);
        EXEC SQL WHENEVER NOT FOUND DO break;
        while (TRUE)
          {
            EXEC SQL COLLECTION GET :cityTable[i] INTO :city;
            PrintCity(city);
          }
        EXEC SQL WHENEVER NOT FOUND CONTINUE;
      }
  }

  EXEC SQL FREE :city;
  EXEC SQL FREE :cityTable;                                           /* 2 */
}


/* 
 * NAME
 *   PrintCountyHeader
 * COLLECTION FEATURES
 *   none
 */
void PrintCountyHeader(county)
  CONST char *county;
{
  printf("\nCOUNTY: %s\n", county);
}

/* 
 * NAME
 *   PrintCity
 * COLLECTION FEATURES
 *   none
 */
void PrintCity(city)
  city_t *city;
{
  varchar newCITY[CITY_NAME_LEN];
  int newPOP;

  EXEC SQL OBJECT GET NAME, POPULATION from :city INTO :newCITY, :newPOP;
  printf("CITY: %.*s POP: %d\n", CITY_NAME_LEN, newCITY.arr, newPOP);
}

/* 
 * NAME
 *   PrintCounty
 * DESCRIPTION
 *   Prints the population and name of each city in a particular county.
 * COLLECTION FEATURES
 *   The following features correspond to the inline commented numbers
 *   1) A ref cursor host variable may be used to scroll through the 
 *      rows of a collection. 
 *   2) Use SQL ALLOCATE/FREE to create and destroy the ref cursor.
 *   3) The "CURSOR" clause in SQL can be used to load a ref cursor 
 *      host variable.  In such a case, the SELECT ... INTO does an
 *      implicit "OPEN" of the ref cursor.
 * IMPLEMENTATION NOTES 
 *   In the case of SQL SELECT statements which contain an embedded 
 *   CURSOR(...) clause, the Pro*C "select_error" flag must be "no"
 *   to prevent cancellation of the parent cursor.
 */
void PrintCounty(countyName)
  CONST char *countyName;
{
  sql_cursor cityCursor;                                              /* 1 */
  city_t *city;

  EXEC SQL ALLOCATE :cityCursor;                                      /* 2 */
  EXEC SQL ALLOCATE :city;                                        
  
  EXEC ORACLE OPTION(select_error=no);
  EXEC SQL SELECT 
    CURSOR(SELECT VALUE(c) FROM TABLE(county_tbl.cities) c)
      INTO :cityCursor 
      FROM county_tbl
      WHERE county_tbl.name = :countyName;                            /* 3 */
  EXEC ORACLE OPTION(select_error=yes);
  
  PrintCountyHeader(countyName);

  EXEC SQL WHENEVER NOT FOUND DO break;
  while (TRUE)
    {
      EXEC SQL FETCH :cityCursor INTO :city;
      PrintCity(city);
    }
  EXEC SQL WHENEVER NOT FOUND CONTINUE;

  EXEC SQL CLOSE :cityCursor;

  EXEC SQL FREE :cityCursor;                                         /* 2 */
  EXEC SQL FREE :city; 
}


/* 
 * NAME
 *   CountyPopulation
 * DESCRIPTION
 *   Returns the number of people living in a particular county.
 * COLLECTION FEATURES
 *   The following features correspond to the inline commented numbers
 *   1) A "DECLARED" cursor may be used to scroll through the 
 *      rows of a collection. 
 *   2) The "THE" clause in SQL is used to convert a single nested-table
 *      column into a table.
 */
long CountyPopulation(countyName)
  CONST char *countyName;
{
  long population;
  long populationTotal = 0;

  EXEC SQL DECLARE cityCursor CURSOR FOR
    SELECT c.population 
    FROM THE(SELECT cities FROM county_tbl 
              WHERE name = :countyName) AS c;                     /* 1, 2 */

  EXEC SQL OPEN cityCursor;

  EXEC SQL WHENEVER NOT FOUND DO break;
  while (TRUE)
    {
      EXEC SQL FETCH cityCursor INTO :population;
      populationTotal += population;
    }
  EXEC SQL WHENEVER NOT FOUND CONTINUE;

  EXEC SQL CLOSE cityCursor;
  return populationTotal;
}


/* 
 * NAME
 *   PrintSQLError
 * DESCRIPTION
 *   Prints an error message using info in sqlca and calls exit.
 * COLLECTION FEATURES
 *   none
 */ 
void PrintSQLError()
{
  EXEC SQL WHENEVER SQLERROR CONTINUE;
  printf("SQL error occurred...\n");
  printf("%.*s\n", (int)sqlca.sqlerrm.sqlerrml,
         (CONST char *)sqlca.sqlerrm.sqlerrmc);
  EXEC SQL ROLLBACK RELEASE;
  exit(EXIT_FAILURE);
}