日本語PDF

14 ANSI動的SQL

この章では、新しい方法4アプリケーションに使用するANSI動的SQL(SQL標準動的SQLとも呼ばれます)のOracleでの実装を説明します。これは、従来のOracle動的SQL方法4に拡張機能を加えたものです。詳細は、前の章の「Oracle動的SQL: 方法4」を参照してください。

ANSI方法4では、すべてのOracle型がサポートされます。従来のOracle方法4では、オブジェクト型、カーソル変数、構造体の配列、DML RETURNING句、Unicode変数およびLOBはサポートされませんでした

従来のOracle動的SQL方法4では記述子はユーザーのPro*C/C++プログラムで定義されますが、ANSI動的SQLでは記述子はOracle内部で管理されます。どちらの場合でも、方法4では、様々な数のホスト変数を含むSQL文をPro*C/C++プログラムで受け取ったり作成したりできます。

この章のトピックは、次のとおりです:

14.1 ANSI動的SQLの基礎

次のSQL文について考えます。

SELECT ename, empno FROM emp WHERE deptno = :deptno_data 

ANSI動的SQLを使用する手順は、次のとおりです。

  • 実行する文を格納する文字列などの変数を宣言します。

  • 入力変数および出力変数に記述子を割り当てます。

  • 文を準備します。

  • 入力記述子の入力を記述します。

  • 入力記述子を設定します(この例では1つの入力ホスト・バインド変数deptno_data)。

  • 動的カーソルを宣言およびオープンします。

  • 出力記述子(上の例の出力ホスト変数は、enameおよびempno)を設定します。

  • enameおよびempnoデータ・フィールドを各行から取り出すために、GET DESCRIPTORを使用してデータを繰り返しフェッチします。

  • 取り出したデータを処理します(出力など)。

  • 動的カーソルを閉じ、入力記述子および出力記述子への割当てを解除します。

14.1.1 プリコンパイラのオプション

マイクロ・プリコンパイラ・オプションDYNAMICをANSIに設定するか、マクロ・オプションMODEをANSIに設定してください。これにより、DYNAMICのデフォルト値がANSIに設定されます。DYNAMICのもう1つの設定はORACLEです。

ANSI型コードを使用するには、TYPE_CODEプリコンパイラ・マイクロ・オプションをANSIに設定するか、MODEマクロ・オプションをANSIに設定します。これにより、TYPE_CODEのデフォルト設定がANSIに変更されます。TYPE_CODEをANSIに設定するには、DYNAMICもANSIに設定する必要があります。

「ANSI SQL文の概要」に記載されているANSI SQL型のOracleによる実装は、ANSI規格と完全には一致していません。たとえば、INTEGERとして宣言された列の記述では、NUMERICのコードが戻されます。OracleをANSI規格に近づけると、動作にわずかな変更が必要になる場合があります。使用中のアプリケーションをデータベース・プラットフォーム間で移植できるようにして、可能なかぎりANSI準拠にする場合、TYPE_CODEプリコンパイラ・オプションを設定したANSI型を使用してください。このような変更ができない場合は、TYPE_CODEをANSIに設定しないでください。

14.2 ANSI SQL文の概要

動的SQL文で記述子を使用する前に、記述子領域を割り当てます。

ALLOCATE DESCRIPTOR文の構文は次のとおりです。

EXEC SQL ALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} 
  [WITH MAX {:occurrences | numeric_literal}];

グローバル記述子は、プログラム内の任意のモジュールで使用できます。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。デフォルトはローカルです。

記述子名のdesc_namには、引用符で囲んだリテラルまたはホスト変数に格納した文字値を代入できます。

occurrencesは、記述子が保持できるバインド変数または列数の最大値です。数値リテラルを指定する必要があります。デフォルトは100です。

記述子が必要なくなった場合は、割当てを解除してメモリーを節約します。それ以外の場合には、アクティブなデータベース接続がなくなった時点で自動的に割当てが解除されます。

割当て解除文は次のとおりです。

EXEC SQL DEALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal};

準備済のSQL文に関する情報を取得するには、DESCRIBE文を使用します。DESCRIBE INPUTでは、準備済の動的文のバインド変数が記述されます。DESCRIBE OUTPUT (デフォルト)では、出力列の番号、型および長さが記述されます。単純化した構文は次のとおりです。

EXEC SQL DESCRIBE [INPUT | OUTPUT] sql_statement 
    USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal};

SQL文に入力値および出力値がある場合は、入力値および出力値に1つずつ記述子を割り当てる必要があります。入力値がない次の例のような場合があります。

SELECT ename, empno FROM emp ;

この場合、入力記述子は必要ありません。

SELECT文のINSERTS、UPDATES、DELETESおよびWHERE句の入力値を指定するには、SET DESCRIPTOR文を使用します。入力記述子内にDESCRIBEしていないときに入力バインド変数の数(COUNTに格納されています)を設定するには、SET DESCRIPTOR文を使用します。

EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}
   COUNT = {:kount | numeric_literal};

kountには、ホスト変数または数値リテラル(5など)を指定できます。SET DESCRIPTOR文を使用して、各ホスト変数に少なくともデータ・ソースを指定してください。

EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} 
   VALUE item_number DATA = :hv3;

また、次のように入力ホスト変数の型および長さを設定することもできます。

注意:

TYPE_CODE=ORACLEのとき、SET文を使用して明示的に、またはDESCRIBE OUTPUTによって暗黙的にTYPEおよびLENGTHを指定していない場合、プリコンパイラではホスト変数から導出された値が使用されます。TYPE_CODE=ANSIのときは、表14-1の値を使用してTYPEを設定する必要があります。また、ANSIデフォルト長はホスト変数に一致しないことがあるため、LENGTHも設定する必要があります。

EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} 
   VALUE item_number TYPE = :hv1, LENGTH = :hv2, DATA = :hv3;

hv1hv2およびhv3 などの識別子は、ホスト変数から値を供給する必要があることをユーザーが忘れないようにするために使用します。item_numberは、入力変数のSQL文内での位置を表します。

TYPE_CODEをANSIに設定した場合は、次の表の型コードからTYPEを選択します。

表14-1 ANSI SQLデータ型

データ型 型コード

CHARACTER

1

CHARACTER VARYING

12

DATE

9

DECIMAL

3

DOUBLE PRECISION

8

FLOAT

6

INTEGER

4

NUMERIC

2

REAL

7

SMALLINT

5

DATAは、入力されるホスト変数の値です。

インジケータ、精度、位取りなど、その他の入力値を設定することもできます。

SET DESCRIPTOR文の数値は、intまたはshort intで宣言する必要があります。ただし、インジケータおよび戻された長さの値はshort intとして宣言する必要があります。

たとえば、次の例でempnoを取得する場合、empnoは動的SQL文の2番目の出力ホスト変数であるため、値をVALUE = 2に設定します。ホスト変数empno_typは、3(Oracleタイプの整数値)に設定します。ホスト整数の長さを表すempno_lenは、4に設定します。この値はホスト変数のサイズです。DATAはホスト変数empno_dataと等しくなります。この変数は値をデータベース表から受け取ります。コード例は、次のようになります。

...
char *dyn_statement = "SELECT ename, empno FROM emp 
   WHERE deptno = :deptno_number" ;
int empno_data ;
int empno_typ = 3 ;
int empno_len = 4 ;
...
EXEC SQL SET DESCRIPTOR 'out' VALUE 2  TYPE = :empno_typ, LENGTH = :empno_len,
   DATA = :empno_data ;

入力値を設定後に、入力記述子を使用して文を実行またはオープンします。文中に出力値がある場合、FETCHを行う前に出力値を設定してください。DESCRIBE OUTPUTを実行している場合は、ホスト変数の実際の型と長さのテストが必要になる場合があります。DESCRIBEを実行すると、ホスト変数の外部型および長さとは異なる内部型と長さが生成されます。

出力記述子をFETCHした後、戻されたデータにアクセスするには、GET DESCRIPTORを使用します。簡略化された構文は次のとおりです。構文の詳細は、この章の後半部分を参照してください。

EXEC SQL GET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}
   VALUE item_number :hv1 = DATA, :hv2 = INDICATOR, :hv3 = RETURNED_LENGTH ;

desc_namおよびitem_numberには、リテラルまたはホスト変数を指定できます。記述子名には、リテラル(outなど)を指定できます。項目番号には、数値リテラル(2など)を指定できます。

hv1、hv2およびhv3は、それぞれホスト変数です。ここにはホスト変数を指定する必要があり、リテラルは指定できません。例では、戻されるデータを3つのみ示しています。

数値すべてにlongintまたはshortのいずれかを指定します。ただし、インジケータまたは戻された長さの値はshortにする必要があります。

関連項目:

  • Oracle型コードは表15-2を参照してください

  • 可能な記述子項目名の詳細は、SET DESCRIPTORを参照してください。

  • 戻されたデータから取得できるすべての項目の一覧については、表14-4を参照してください。

14.2.1 サンプル・コード

次の例は、ANSI動的SQLの使用方法を示しています。ここでは入力記述子('in')および出力記述子('out')を割り当ててSELECT文を実行します。入力値はSET DESCRIPTOR文を使用して設定します。カーソルはオープンおよびフェッチされ、結果の出力値はGET DESCRIPTOR文を使用して取得されます。

...
char* dyn_statement = "SELECT ename, empno FROM emp WHERE deptno = :deptno_data" ;
int deptno_type = 3, deptno_len = 2, deptno_data = 10 ;
int ename_type = 97, ename_len = 30 ;
char ename_data[31] ;
int empno_type = 3, empno_len = 4 ;
int empno_data ;
long SQLCODE = 0 ;
...
main ()
{
/* Place preliminary code, including connection, here. */
...
EXEC SQL ALLOCATE DESCRIPTOR 'in' ;
EXEC SQL ALLOCATE DESCRIPTOR 'out' ;
EXEC SQL PREPARE s FROM :dyn_statement ;
EXEC SQL DESCRIBE INPUT s USING DESCRIPTOR 'in' ;
EXEC SQL SET DESCRIPTOR 'in' VALUE 1 TYPE = :deptno_type,
   LENGTH = :deptno_len, DATA = :deptno_data ;
EXEC SQL DECLARE c CURSOR FOR s ;
EXEC SQL OPEN c USING DESCRIPTOR 'in' ;
EXEC SQL DESCRIBE OUTPUT s USING DESCRIPTOR 'out' ;
EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE = :ename_type, 
    LENGTH = :ename_len, DATA = :ename_data ;
EXEC SQL SET DESCRIPTOR 'out' VALUE 2 TYPE = :empno_type, 
    LENGTH = :empno_len, DATA = :empno_data ;

EXEC SQL WHENEVER NOT FOUND DO BREAK ;
while (SQLCODE == 0) 
{
   EXEC SQL FETCH c INTO DESCRIPTOR 'out' ;
   EXEC SQL GET DESCRIPTOR 'out' VALUE 1 :ename_data = DATA ;
   EXEC SQL GET DESCRIPTOR 'out' VALUE 2 :empno_data = DATA ;
   printf("\nEname = %s Empno = %s", ename_data, empno_data) ;
}
EXEC SQL CLOSE c ;
EXEC SQL DEALLOCATE DESCRIPTOR 'in' ;
EXEC SQL DEALLOCATE DESCRIPTOR 'out' ;
...
}

ANSI動的SQLとともにスクロール可能カーソルを使用することもできます。ANSI動的SQLをスクロール可能カーソルとともに使用するには、カーソルをSCROLLモードでDECLAREします。FETCH文に様々なフェッチ方向を使用して、結果セットにアクセスします。

14.3 Oracle拡張機能

この項では、次の拡張機能を説明します。

  • SET文のデータ項目の参照セマンティクス。

  • バルク操作のための配列。

  • オブジェクト型、NCHAR列およびLOBのサポート。

14.3.1 参照セマンティクス

ANSI規格では、構文が指定されています。パフォーマンス向上のために、Oracleではこの規格を拡張して参照セマンティクスを導入しています。

値構文では、ホスト変数データのコピーを作成します。参照セマンティクスでは、ホスト変数のアドレスを使用し、コピーは行いません。そのため、参照セマンティクスを使用すると、大量データ処理のパフォーマンスが向上します。

フェッチの速度向上には、データ句の前にREFキーワードを使用します。

EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE = :ename_type,
   LENGTH = :ename_len, REF DATA = :ename_data ;
EXEC SQL DESCRIPTOR 'out' VALUE 2 TYPE = :empno_type,
   LENGTH = :empno_len, REF DATA = :empno_data ;

これにより、取り出された結果がホスト変数に渡されます。GET文は必要ありません。FETCHが実行されるたびに、取り出されたデータは、ename_dataおよびempno_dataに直接書き込まれます。

次のコード例に示すように、REFキーワードが使用できるのは、DATA、INDICATORおよびRETURNED_LENGTH項目(フェッチする行によって変わる可能性があります)の前に限られます

int indi, returnLen ;
...
EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE = :ename_type,
   LENGTH = :ename_len, REF DATA = :ename_data,
      REF INDICATOR = :indi, REF RETURNED_LENGTH = :returnLen ;

フェッチするたびに、returnLenによりenameフィールドの実際に取得した長さが保持されます。このフィールドはCHARまたはVARCHAR2データで使用すると便利です。

ename_lenには取り出された長さは渡されません。これは、FETCH文によって変更されません。データの行をフェッチする前に列の最大幅を調べるには、GET文の前にDESCRIBEを使用します。

REFキーワードは、SELECT以外のSQL文の処理速度向上のためにも使用します。参照セマンティクスの場合、記述子領域にコピーされた値ではなくホスト変数が使用されます。SQL文を実行する時点でのホスト変数データが使用されるのであって、SETの時点でのデータではありません。次はその例です。

int x = 1 ;
EXEC SQL SET DESCRIPTOR 'value' VALUE 1 DATA = :x ;
EXEC SQL SET DESCRIPTOR 'reference' VALUE 1 REF DATA = :x ;
x = 2 ;
EXEC SQL EXECUTE s USING  DESCRIPTOR 'value' ;    /* Will use  x = 1 */
EXEC SQL EXECUTE s USING DESCRIPTOR 'reference' ; /* Will use x = 2 */

関連項目

14.3.2 配列を使用したバルク操作について

Oracleによりバルク操作機能が追加され、ANSI動的SQLが拡張されました。バルク操作を行うには、処理する入力データ量または行数を指定するために、FOR句で配列サイズを指定します。

FOR句は、ALLOCATE文で最大データ量または最大行数の指定に使用します。最大配列サイズ100を指定するには、次のように記述します。

EXEC SQL FOR 100 ALLOCATE DESCRIPTOR 'out' ;

または

int array_size = 100 ;
...
EXEC SQL FOR :array_size ALLOCATE DESCRIPTOR 'out' ;

FOR句は、記述子にアクセスする後続の文で使用されます。次に示すように、出力記述子では、ALLOCATE文で指定した配列サイズと等しいか、それよりも小さい配列サイズをFETCH文に割り当てる必要があります。

EXEC SQL FOR 20 FETCH c1 USING DESCRIPTOR 'out' ;

後続の、同じ記述子のDATA、INDICATORまたはRETURNED_LENGTH値を取得するGET文では、FETCH文と同じ配列サイズを指定する必要があります。

int val_data[20] ;
short val_indi[20] ;
...
EXEC SQL FOR 20 GET DESCRIPTOR 'out' VALUE 1 :val_data = DATA,
  :val_indi = INDICATOR ;

ただし、LENGTH、TYPE、COUNTなど、行によって変化しない項目を参照するGET文では、FOR句は使用しないでください

int cnt, len ;
...
EXEC SQL GET DESCRIPTOR 'out' :cnt = COUNT ;
EXEC SQL GET DESCRIPTOR 'out' VALUE 1 :len = LENGTH ;

これは、参照セマンティクスを使用したSET文でも同じです。FETCHの前にあり、DATA、INDICATORまたはRETURNED_LENGTHに対する参照セマンティクスを使用したSET文には、FETCHと同じ配列サイズを指定する必要があります。

int ref_data[20] ;
short ref_indi[20] ;
...
EXEC SQL FOR 20 SET DESCRIPTOR 'out' VALUE 1 REF DATA = :ref_data,
   REF INDICATOR = :ref_indi ;

同様に、行のバッチの挿入など、入力に使用する記述子でも、ALLOCATE文で使用した配列サイズと等しいか、それよりも小さいサイズの配列サイズをEXECUTEまたはOPEN文に使用する必要があります。値および参照セマンティクスのどちらも、DATA、INDICATORまたはRETURNED_LENGTHにアクセスするSET文では、EXECUTE文と同じ配列サイズを使用する必要があります。

FOR句は、DEALLOCATEまたはPREPARE文では使用しません。

次のコード例に、出力記述子のないバルク操作の例を示します(出力はなく、表empに挿入する入力のみあります)。COUNTの値は2です(INSERT文にename_arrおよびempno_arrの2つのホスト変数があることを示します)。データ配列ename_arrには、順番に「Tom」、「Dick」および「Harry」という3つの文字列が保持されています。インジケータ配列ename_indには2つ目の要素に-1の値を設定しているため、「Dick」ではなくNULLが挿入されます。データ配列empno_arrには、従業員番号が3つ含まれています。DML RETURNING句を使用すると、実際に挿入された名前を確認できます。

...
char* dyn_statement = "INSERT INTO emp (ename) VALUES (:ename_arr)" ;
char ename_arr[3][6] = {Tom","Dick","Harry"} ;
short ename_ind[3] = {0,-1,0} ;
int ename_len = 6, ename_type = 97, cnt = 2 ;
int empno_arr[3] = {8001, 8002, 8003} ;
int empno_len = 4 ;
int empno_type = 3 ;
int array_size = 3 ;
EXEC SQL FOR :array_size ALLOCATE DESCRIPTOR 'in' ;
EXEC SQL SET DESCRIPTOR 'in' COUNT = :cnt ;
EXEC SQL SET DESCRIPTOR 'in' VALUE 1 TYPE = :ename_type, LENGTH = :ename_len ;
EXEC SQL SET DESCRIPTOR 'in' VALUE 2 TYPE = :empno_type, LENGTH = :empno_len ;
EXEC SQL FOR :array_size SET DESCRIPTOR 'in' VALUE 1
   DATA = :ename_arr, INDICATOR = :ename_ind ;
EXEC SQL FOR :array_size SET DESCRIPTOR 'in' VALUE 2
   DATA = :empno_arr ;
EXEC SQL PREPARE s FROM :dyn_statement ;
EXEC SQL FOR :array_size EXECUTE s USING DESCRIPTOR 'in' ;
...

上のコードを実行すると、次の値が挿入されます。

EMPNO   ENAME
 8001   Tom
 8002   
 8003   Harry

14.3.3 構造体配列のサポート

HOST_STRIDE_LENGTHを構造体のサイズに、INDICATOR_STRIDE_LENGTHをインジケータ構造体のサイズに、そしてRETURNED_LENGTH_STRIDEを戻された長さの構造体のサイズに設定する必要があります。

構造体の配列は、ANSI動的SQLによってサポートされていますが、従来のOracle動的SQLではサポートされていません

14.3.4 オブジェクト型のサポート

独自に定義したオブジェクト型では、Oracle TYPEを108にして使用してください。オブジェクト型の列では、DESCRIBE文を使用してUSER_DEFINED_TYPE_VERSION、USER_DEFINED_TYPE_NAME、USER_DEFINED_TYPE_NAME_LENGTH、USER_DEFINED_TYPE_SCHEMAおよびUSER_DEFINED_TYPE_SCHEMA_LENGTHを取得します。

DESCRIBE文を使用しないでこれらの値を取得する場合は、SET DESCRIPTOR文を使用して自分で設定を行う必要があります。

14.4 ANSI動的SQLのプリコンパイラ・オプション

マクロ・オプションのMODEを使用すると、ANSIと互換性のある特性の設定や、多くの機能の制御ができます。値にはANSIまたはORACLEを設定できます。個々の機能には、MODE設定に優先するマイクロ・オプションがあります。

動的SQLでの記述子の動作を指定する場合は、プリコンパイラ・マイクロ・オプションDYNAMICを使用します。ANSIとORACLEのどちらのデータ型を使用するかを指定する場合は、プリコンパイラ・マイクロ・オプションTYPE_CODEを使用します。

マクロ・オプションMODEをANSIに設定すると、マイクロ・オプションDYNAMICは自動的にANSIになります。MODEをORACLEに設定すると、DYNAMICはORACLEになります。

DYNAMICおよびTYPE_CODEはインラインでは使用できません。

次の表に機能とDYNAMICの設定がその機能に与える影響を示します。

表14-2 DYNAMICオプションの設定

機能 DYNAMIC = ANSI DYNAMIC = ORACLE

記述子の作成。

ALLOCATE文を使用する必要があります。

関数SQLSQLDAAlloc()を使用する必要があります。

記述子の破壊。

DEALLOCATE文を使用できます。

関数SQLLDAFree()が使用可能です。

データの取得。

FETCH文およびGET文のどちらも使用できます。

FETCH文のみ使用できます。

入力データの設定。

DESCRIBE INPUT文を使用できます。SET文を使用する必要があります。

コードに記述子値を設定する必要があります。DESCRIBE BIND VARIABLES文を使用する必要があります。

記述子の表現。

引用符付きのリテラル、または記述子名を含むホスト識別子。

ホスト変数、SQLDAを指すポインタ。

利用可能なデータ型。

BITを除くすべてのANSI型、およびすべてのOracle型。

オブジェクト、LOB、構造体の配列およびカーソル変数以外のOracle型。

マイクロ・オプションTYPE_CODEは、プリコンパイラによってマクロ・オプションMODEと同じ値に設定されます。DYNAMICがANSIの場合、TYPE_CODEはANSI以外には設定できません。

TYPE_CODE設定に対応する機能は次のとおりです。

表14-3 TYPE_CODEオプションの設定

機能 TYPE_CODE = ANSI TYPE_CODE = ORACLE

動的SQLからの入出力に使用するデータ型コード番号。

ANSI型があるときはANSIコード番号を使用します。ない場合はOracleコード番号の負の値を使用します。

DYNAMIC = ANSIのときにのみ有効です。

Oracleコード番号を使用します。

DYNAMICの設定に関係なく使用できます。

14.5 動的SQL文の構文

ここで説明するすべての文の詳細は、埋込みSQL文およびディレクティブを参照してください。

14.5.1 ALLOCATE DESCRIPTOR

用途

この文は、SQL記述子領域を割り当てるために使用します。記述子、ホスト・バインド項目発生数の最大値および配列サイズを指定します。この文は、ANSI動的SQLでのみ使用できます。

構文

EXEC SQL [FOR [:]array_size] ALLOCATE DESCRIPTOR [GLOBAL | LOCAL]
   {:desc_nam | string_literal} [WITH MAX occurrences] ;

変数

array_size

これは配列処理をサポートするオプション句(Oracle拡張機能)です。この句により、配列処理で記述子が使用可能であることがプリコンパイラに通知されます。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子の名前。ローカル記述子は、モジュール内で一意にしてください。前回の割当てを解除せずに記述子を割り当てた場合は、ランタイム・エラーが生成されます。グローバル記述子は、アプリケーション全体で一意にしてください。そうでない場合はランタイム・エラーが発生します。

occurrences

記述子で使用可能なホスト変数の最大数です。この値は、0から64KBの整数定数にする必要があります。それ以外の場合はエラーが戻されます。デフォルトは100です。この句はオプションです。これらの規則に違反するとプリコンパイラ・エラーが戻されます。

EXEC SQL ALLOCATE DESCRIPTOR 'SELDES' WITH MAX 50 ;

EXEC SQL FOR :batch ALLOCATE DESCRIPTOR GLOBAL :binddes WITH MAX 25 ;

14.5.2 DEALLOCATE DESCRIPTOR

用途

以前に割り当てられたSQL記述子の割当てを解除してメモリーを解放する場合に、この文を使用します。この文は、ANSI動的SQLでのみ使用できます。

構文

EXEC SQL DEALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} ;

変数

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

同じ名前およびスコープの記述子が割り当てられていない場合、または割当てが解除されている場合は、ランタイム・エラーが発生します。

EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL 'SELDES' ;

EXEC SQL DEALLOCATE DESCRIPTOR :binddes ;

14.5.3 GET DESCRIPTOR

用途

この文は、SQL記述子領域からの情報の取得に使用します。

構文

EXEC SQL [FOR [:]array_size] GET DESCRIPTOR [GLOBAL | LOCAL] 
   {:desc_nam | string_literal} 
   { :hv0  = COUNT | VALUE item_number 
      :hv1 = item_name1 [ {, :hvN = item_nameN}] } ;

変数

array_size

FOR array_sizeは、オプションのOracle拡張機能です。array_sizeは、FETCH文のarray_sizeフィールドと等しくする必要があります。

COUNT

バインド変数の合計数。

desc_nam

記述子の名前。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

VALUE item_number

SQL文内での項目の位置。item_numberには変数または定数を指定できます。item_numberの値がCOUNTより大きい場合、「データが見つかりません。」という条件が戻されます。item_numberには0より大きい値を指定する必要があります。

hv1 .. hvN

値の転送先のホスト変数。

item_name1 .. item_nameN

ホスト変数に対応付けられた記述子項目名。使用可能なANSI記述子項目名は次のとおりです。

表14-4 GET DESCRIPTORの記述子項目名の定義

記述子項目名 意味

TYPE

ANSIデータ型が表にない場合およびTYPE_CODE=ANSIの場合は、負の値のOracle型コードを使用してください。

LENGTH

列データの長さ。NCHARでは文字数、その他の場合はバイト数で表されます。DESCRIBE OUTPUTによって設定されます。

OCTET_LENGTH

バイト単位でのデータの長さ。

RETURNED_LENGTH

FETCH後の実際のデータ長。

RETURNED_OCTET_LENGTH

戻されたデータのバイト単位での長さ。

PRECISION

桁数。

SCALE

真数値型での小数点の右側の桁数。

NULLABLE

1のときは、列にNULL値を使用できます。0の場合、列にはNULL値を指定できません。

INDICATOR

対応付けられたインジケータ値。

DATA

データの値。

NAME

列の名前。

CHARACTER_SET_NAME

列のキャラクタ・セット。

追加されたOracle記述子項目の名前は次のとおりです。

表14-5 Oracle拡張機能により追加されたGET DESCRIPTORの記述子項目名の定義

記述子項目名 意味

NATIONAL_CHARACTER

2はNCHARまたはNVARCHAR2を示します。1の場合は文字です。0の場合は文字以外の値です。

INTERNAL_LENGTH

内部でのバイト単位の長さ。

HOST_STRIDE_LENGTH

ホスト構造体のサイズ。バイト数で表します。

INDICATOR_STRIDE_LENGTH

インジケータ構造体のサイズ。バイト数で表します。

RETURNED_LENGTH_STRIDE

戻された長さの構造体のサイズ。バイト数で表します。

USER_DEFINED_TYPE_VERSION

オブジェクト型バージョンを表す文字。

USER_DEFINED_TYPE_NAME

オブジェクト型の名前。

USER_DEFINED_TYPE_NAME_LENGTH

オブジェクト型の名前の長さ。

USER_DEFINED_TYPE_SCHEMA

オブジェクト・スキーマを表す文字。

USER_DEFINED_TYPE_SCHEMA_LENGTH

USER_DEFINED_TYPE_SCHEMAの長さ。

NATIONAL_CHARACTER

2はNCHARまたはNVARCHAR2を示します。1の場合は文字です。0の場合は文字以外の値です。

使用上の注意

FOR句は、DATA、INDICATORおよびRETURNED_LENGTH項目のみを含むGET DESCRIPTOR文で使用してください。

内部型は、DESCRIBE OUTPUT文によって設定されます。入力および出力のどちらでも、ホスト変数の外部型に使用する型を設定する必要があります。

TYPEはANSI SQLデータ型のコードです。ANSIの型が表に含まれていない場合は、負の値のOracle型コードを使用してください。

LENGTHには、固定幅の各国語キャラクタ・セットを持つフィールドの列の長さを表す文字数が含まれます。それ以外のキャラクタ列ではバイト単位になります。LENGTHはDESCRIBE OUTPUTによって設定されます。

RETURNED_LENGTHは、FETCH文によって設定される実際のデータ長です。LENGTHと同様にバイト単位または文字単位になります。フィールドOCTET_LENGTHおよびRETURNED_OCTET_LENGTHは、バイト単位の長さです。

NULLABLE = 1は、列にNULLを使用できることを示します。NULLABLE = 0は、列にNULLを使用できないことを示します。

CHARACTER_SET_NAMEは、キャラクタ列の場合にのみ意味があります。他の型では未定義になります。DESCRIBE OUTPUT文によって値が設定されます。

DATAおよびINDICATORは、その列のデータ値およびインジケータ・ステータスです。データがNULLでインジケータが要求されなかった場合は、実行時にエラーが発生します(DATA EXCEPTION, NULL VALUE, NO INDICATOR PARAMETER)。

Oracle固有の記述子項目名

列がNCHARまたはNVARCHAR2列の場合は、NATIONAL_CHARACTER = 2になります。列がキャラクタ(ただし、各国語キャラクタではない)列の場合、項目は1に設定されます。キャラクタ以外の列の場合、DESCRIBE OUTPUTの実行後にこの項目は0に設定されます。

INTERNAL_LENGTHは、Oracle動的方法4との互換性があるため、Oracle記述子領域の長さメンバーと同じ値に設定されます。この項目にはOracle SQL記述子領域の長さメンバーと同じ値が設定されています。

次の3つの項目はDESCRIBE OUTPUT文によって戻されません。

  • ホスト変数構造体のサイズを示すHOST_STRIDE_LENGTH。

  • 標識変数の構造体のサイズを示すINDICATOR_STRIDE_LENGTH。

  • 戻された長さの変数構造体のサイズを示すRETURNED_LENGTH_STRIDE

次の項目は、プリコンパイラ・オプションOBJECTSがYESに設定されているときにのみオブジェクト型に適用されます。

  • タイプ・バージョンを表す文字を含むUSER_DEFINED_TYPE_VERSION。

  • 型の名前を表す文字を示すUSER_DEFINED_TYPE_NAME。

  • 型の名前の長さをバイト数で示すUSER_DEFINED_TYPE_NAME_LENGTH。

  • 型のスキーマ名を表す文字を示すUSER_DEFINED_TYPE_SCHEMA。

  • 型のスキーマ名の長さを文字数で示すUSER_DEFINED_TYPE_SCHEMA_LENGTH。

EXEC SQL GET DESCRIPTOR :binddes :n = COUNT ;

EXEC SQL GET DESCRIPTOR 'SELDES' VALUE 1 :t = TYPE, :l = LENGTH ;

EXEC SQL FOR :batch GET DESCRIPTOR LOCAL 'SELDES'
   VALUE :sel_item_no :i = INDICATOR, :v = DATA ; 

関連項目:

14.5.4 SET DESCRIPTOR

用途

この文は、ホスト変数からの記述子領域の情報を設定するために使用します。SET DESCRIPTOR文では、項目名のホスト変数のみサポートされます。

構文

EXEC SQL [FOR array_size] SET DESCRIPTOR [GLOBAL | LOCAL] 
   {:desc_nam | string_literal} {COUNT = :hv0 | 
   VALUE item_number
   [REF] item_name1 = :hv1 
   [{, [REF] item_nameN = :hvN}]} ;

変数

array_size

このOracleオプション句で配列を使用できるのは、記述子項目DATA、INDICATORおよびRETURNED_LENGTHの設定時のみです。FOR句を含むSET DESCRIPTORでは他の項目を使用できません。ホスト変数配列サイズはすべて一致している必要があります。FETCH文で使用するのと同じ配列サイズをSET文で使用してください。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子名。ALLOCATE DESCRIPTORでの規則が適用されます。

COUNT

バインド(入力)変数または定義(出力)変数の数。

VALUE item_number

動的SQL文でのホスト変数の位置。

hv1 .. hvN

設定するホスト変数(定数ではありません)。

item_nameI

desc_item_nameでは、GET DESCRIPTOR構文と同様の方法でこれらの値が使用されます。

表14-6 SET DESCRIPTORの記述子項目名

記述子項目名 意味

TYPE

対応するANSI型がない場合は、負の値のOracle型を使用します。

LENGTH

列内のデータの最大長。

INDICATOR

対応付けられたインジケータ値。参照セマンティクスのために設定します。

DATA

設定するデータの値。参照セマンティクスのために設定します。

CHARACTER_SET_NAME

列のキャラクタ・セット。

TYPE

対応するANSI型がない場合は、負の値のOracle型を使用します。

ANSI型コードについては表14-1、Oracle型コードについては表15-2をそれぞれ参照してください。

Oracle拡張機能により追加された記述子項目の名前は次のとおりです。

表14-7 Oracle拡張機能により追加されたSET DESCRIPTORの記述子項目名の定義

記述子項目名 意味

RETURNED_LENGTH

FETCH後に戻される長さ。参照セマンティクスを使用する場合に設定します。

NATIONAL_CHARACTER

入力ホスト変数がNCHARまたはNVARCHAR2型のときは、2に設定します。

各国語キャラクタ設定が設定されていない場合、0に設定します。

HOST_STRIDE_LENGTH

ホスト変数構造体のサイズ。バイト数で表します。

INDICATOR_STRIDE_LENGTH

標識変数のサイズ。バイト数で表します。

RETURNED_LENGTH_STRIDE

戻された長さの構造体のサイズ。バイト数で表します。

USER_DEFINED_TYPE_NAME

オブジェクト型の名前。

USER_DEFINED_TYPE_NAME_LENGTH

オブジェクト型の名前の長さ。

USER_DEFINED_TYPE_SCHEMA

オブジェクト・スキーマを表す文字。

USER_DEFINED_TYPE_SCHEMA_LENGTH

USER_DEFINED_TYPE_SCHEMAの長さ。

使用上の注意

参照セマンティクスは、パフォーマンス向上のために使用する別のオプションのOracle拡張機能です。記述子項目名がDATA、INDICATORおよびRETURNED_LENGTHの場合にのみ、それらの前にREFキーワードを指定します。REFキーワードを使用した場合は、GET文を使用する必要はありません。複合データ型(オブジェクト型、コレクション型、構造体の配列およびDML RETURNING句)はすべて、SET DESCRIPTORのREF形式を必要とします。

プログラムで別のSQLのDESCRIPTORを再利用する場合、DESCRIPTORの古い値は残ります。

REFを使用すると、対応付けられたホスト変数自体がSETで使用されます。この場合、GETは必要ありません。値構文ではなく、REFセマンティクスを使用するときにのみ、RETURNED_LENGTHを設定できます。

SETまたはGET文の配列サイズは、FETCHで使用する配列サイズと同じにしてください。

NCHARホスト変数には、NATIONAL_CHARフィールドを2に設定します。

古いSQLでNCHARホスト入力値用にDESCRIPTORが使用されるとき、NATIONAL_CHARACTERフィールドを0に設定します。

オブジェクト型の特性を設定するときは、USER_DEFINED_TYPE_NAMEおよびUSER_DEFINED_TYPE_NAME_LENGTHを設定する必要があります。

省略した場合は、USER_DEFINED_TYPE_SCHEMAおよびUSER_DEFINED_TYPE_SCHEMA_LENGTHはデフォルトで現行の接続に設定されます。

クライアント側でUnicodeをサポートするには、CHARACTER_SET_NAMEにUTF16を設定します。データはUCS2エンコーディングになり、RETURNED_LENGTHはCHARSです。

int bindno = 2 ;
short indi = -1 ;
char data = "ignore" ;
int batch = 1 ;

EXEC SQL FOR :batch ALLOCATE DESCRIPTOR 'binddes' ;
EXEC SQL SET DESCRIPTOR GLOBAL :binddes COUNT = 3 ;
EXEC SQL FOR :batch SET DESCRIPTOR :bindes
   VALUE :bindno INDICATOR = :indi, DATA = :data ;
...

関連項目:

14.5.5 PREPAREの使用

用途

この方法で使用されているPREPARE文は、他の動的SQL方法で使用されているPREPARE文と同じです。Oracle拡張機能によって、変数と同様にSQL文で引用符付き文字列を使用できます。

構文

EXEC SQL PREPARE statement_id FROM :sql_statement ;

変数

statement_id

これを宣言しないでください。これは未宣言のSQL識別子です。

sql_statement

埋込みSQL文を格納する文字列(定数または変数)。

char* statement = "SELECT ENAME FROM emp WHERE deptno = :d" ;
EXEC SQL PREPARE S1 FROM :statement ;

14.5.6 DESCRIBE INPUT

用途

この文はバインド変数についての情報を戻します。

構文

EXEC SQL DESCRIBE INPUT statement_id USING [SQL] DESCRIPTOR 
   [GLOBAL | LOCAL] {:desc_nam | string_literal} ;

変数

statement_id

PREPAREおよびDESCRIBE OUTPUTで使用するものと同じです。これを宣言しないでください。これは未宣言のSQL識別子です。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子名。

使用上の注意

DESCRIBE INPUTでは、COUNTおよびNAME項目のみが設定されます。

EXEC SQL DESCRIBE INPUT S1 USING SQL DESCRIPTOR GLOBAL :binddes ;
EXEC SQL DESCRIBE INPUT S2 USING DESCRIPTOR 'input' ;

14.5.7 DESCRIBE OUTPUT

用途

PREPAREされた文の出力列についての情報を取得する場合は、この文を使用します。ANSI構文は、旧バージョンのOracle構文と異なります。SQL記述子領域に格納される情報は、戻された値の個数、および関連する情報(型、長さ、名前など)です。

構文

EXEC SQL DESCRIBE [OUTPUT] statement_id USING [SQL] DESCRIPTOR
   [GLOBAL | LOCAL] {:desc_nam | string_literal} ;

変数

statement_id

PREPAREで使用するものと同じです。これを宣言しないでください。これは未宣言のSQL識別子です。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子名。

OUTPUTがデフォルト設定で、これは省略できます。

char* desname = "SELDES" ;
EXEC SQL DESCRIBE S1 USING SQL DESCRIPTOR 'SELDES' ; /* Or, */
EXEC SQL DESCRIBE OUTPUT S1 USING DESCRIPTOR :desname ;

14.5.8 EXECUTE

用途

EXECUTEは、準備済のSQL文の入力変数および出力変数を照合し、文を実行します。EXECUTEのANSIバージョンは、1つの文中に2つの記述子を割り当てることでDML RETURNING句をサポートできる点で、旧バージョンのEXECUTE文とは異なります。

構文

EXEC SQL [FOR :array_size] EXECUTE statement_id 
    [USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}] 
        [INTO [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}] ;

変数

array_size

文が処理する行数。

statement_id

PREPAREで使用するものと同じです。これを宣言しないでください。これは未宣言のSQL識別子です。リテラルを指定できます。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子名。

使用上の注意

INTO句により、INSERT、UPDATEおよびDELETEのDML RETURNING句が実装されます。

EXEC SQL EXECUTE S1 USING SQL DESCRIPTOR GLOBAL :binddes ;

EXEC SQL EXECUTE S2 USING DESCRIPTOR :bv1 INTO DESCRIPTOR 'SELDES' ;

関連項目

14.5.9 EXECUTE IMMEDIATEの使用

用途

リテラルまたはSQL文を含むホスト変数文字列を実行します。この文のANSI SQL形式は、旧バージョンのOracle動的SQLと同じです。

構文

EXEC SQL EXECUTE IMMEDIATE {:sql_statement | string_literal}

変数

sql_statement

文字列内のSQL文またはPL/SQLブロック。

EXEC SQL EXECUTE IMMEDIATE :statement ;

14.5.10 DYNAMIC DECLARE CURSORの使用

用途

問合せ文に対応付けられたカーソルを宣言します。これは、一般的なカーソル宣言部です。

構文

EXEC SQL DECLARE cursor_name CURSOR FOR statement_id;

変数

cursor_name

カーソル変数(SQL識別子であり、ホスト変数ではありません)。

statement_id

未宣言のSQL識別子。

EXEC SQL DECLARE C1 CURSOR FOR S1 ; 

14.5.11 OPENカーソル

用途

OPEN文は、入力パラメータとカーソルとを対応付け、カーソルをオープンします。

構文

EXEC SQL [FOR :array_size] OPEN dyn_cursor 
    [[USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam1 | string_literal}]
    [INTO [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam2 | string_literal}]] ;

変数

array_size

この変数は、記述子を割り当てた時に指定した数と等しいか、それよりも小さい数に制限されます。

dyn_cursor

カーソル変数。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子名。

使用上の注意

カーソルに対応付けられた準備済の文にコロンまたは疑問符が含まれる場合は、USING句を指定する必要があります。指定しない場合は、ランタイム・エラーが発生します。DML RETURNING句がサポートされています。

EXEC SQL OPEN C1 USING SQL DESCRIPTOR :binddes ;

EXEC SQL FOR :limit OPEN C2 USING DESCRIPTOR :b1, :b2 
   INTO SQL DESCRIPTOR :seldes ;

関連項目

14.5.12 FETCH

用途

動的DECLARE文で宣言されたカーソルの行をフェッチします。

構文

EXEC SQL [FOR :array_size] FETCH cursor INTO [SQL] DESCRIPTOR 
   [GLOBAL | LOCAL] {:desc_nam | string_literal} ;

変数

array_size

文が処理する行数。

cursor

事前に宣言された動的カーソル。

GLOBAL | LOCAL

デフォルトでは、オプション句のスコープはLOCALに設定されています。ローカル記述子は、その記述子を割り当てたファイル内でのみアクセス可能です。グローバル記述子は、コンパイル・ユニット内のどのモジュールでも使用できます。

desc_nam

記述子の名前。

使用上の注意

FOR句のarray_sizeオプションは、ALLOCATE DESCRIPTOR文で指定された数以下にする必要があります。

EXEC SQL FETCH FROM C1 INTO DESCRIPTOR 'SELDES' ;

EXEC SQL FOR :arsz FETCH C2 INTO DESCRIPTOR :desc ;

14.5.13 動的カーソルのCLOSE

用途

動的カーソルをクローズします。構文は、旧バージョンのOracle方法4から変更されていません。

構文

EXEC SQL CLOSE cursor ;

変数

cursor

事前に宣言された動的カーソル。

EXEC SQL CLOSE C1 ;

14.5.14 Oracle動的方法4との違い

ANSI動的SQLインタフェースは、Oracle動的方法4がサポートしたすべてのデータ型に加え、次の機能をサポートしています。

  • ANSI動的SQLによるオブジェクト型、結果セットおよびLOB型を含むすべてのデータ型のサポート。

  • ANSIモードでは、内部のSQL記述領域が使用されますが、これは、Oracleの以前の動的方法4で入力および出力情報の格納に使用される外部SQLDAを拡張したものです。

  • ALLOCATE DESCRIPTOR、DEALLOCATE DESCRIPTOR、DESCRIBE、GET DESCRIPTORおよびSET DESCRIPTORという新しい埋込みSQL文が導入されています。

  • ANSI動的SQLでは、DESCRIBE文は標識変数の名前を戻しません。

  • ANSI動的SQLでは、戻される列名または式の最大サイズを指定できません。デフォルト・サイズは128に設定されます。

  • 記述子名は、引用符で囲んだ識別子、または先頭にコロンをつけたホスト変数のどちらかであることが必要です。

  • 出力時に、DESCRIBE文のSELECT LIST FOR句はオプション・キーワードOUTPUTに置き換えられます。INTO句はUSING DESCRIPTOR句に置き換えられます。この句には、オプション・キーワードSQLを含めることができます。

  • 入力時に、DESCRIBE文のオプションのBIND VARIABLES FOR句をキーワードINPUTに置き換えることができます。INTO句はUSING DESCRIPTOR句に置き換えられます。この句には、オプション・キーワードSQLを含めることができます。

  • EXECUTE、FETCHおよびOPEN文でUSING句のキーワードDESCRIPTORの前にオプション・キーワードSQLを指定できます。

14.5.15 制限事項(ANSI動的SQL)

ANSI動的SQLには次の制限が適用されます。

  • 同じモジュール内でANSIおよびOracle動的SQLを組み合せて使用できません。

  • プリコンパイラ・オプションDYNAMICをANSIに設定する必要があります。DYNAMICをANSIに設定したときにのみ、プリコンパイラ・オプションTYPE_CODEをANSIに設定できます。

  • SET文は、項目名としてのホスト変数のみサポートします。

14.6 サンプル・プログラム

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

14.6.1 ansidyn1.pc

このプログラムは、ANSI動的SQLを使用したSQL文の処理方法を示します。このSQL文は実行時まで不明です。これは、ANSI動的SQLを使用するための最も簡単な(ただし、最も効率的というわけではない)方法を紹介することを目的としています。ここでは、ANSIと互換性のある値セマンティクスおよびANSI型コードを使用しています。ANSI SQLSTATEは、エラー番号用に使用されています。記述子名はリテラルです。すべての入力および出力には、ANSI可変キャラクタ・タイプが使用されます。

このプログラムでは、自分のユーザー名とパスワードを使用してORACLEに接続した後、SQL文を入力します。埋込み型ではなく通常のSQL構文を使用して有効なSQLまたはPL/SQL文を入力し、各文の終わりにセミコロンを付けてください。入力した文が処理されます。問合せのときはフェッチされた行が表示されます。

複数行の文を入力できます。最大1023文字まで入力できます。変数のサイズには制限があり、MAX_VAR_LENが255に定義されています。このプログラムでは、バインド変数は40、選択リスト項目も40まで処理できます。DML RETURNING句およびユーザー定義型は、値セマンティクスではサポートされていません。

次のように、mode = ansiに設定してプログラムをプリコンパイルします。

proc mode=ansi ansidyn1

mode=ansiに指定すると、動的およびtype_codeがANSIに設定されます。

/*******************************************************************
ANSI Dynamic Demo 1:  ANSI Dynamic SQL with value semantics,
                                   literal descriptor names
                                   and ANSI type codes

This program demonstates using ANSI Dynamic SQL to process SQL
statements which are not known until runtime.  It is intended to
demonstrate the simplest (though not the most efficient) approach
to using ANSI Dynamic SQL.  It uses ANSI compatible value semantics
and ANSI type codes. ANSI Sqlstate is used for error numbers. 
Descriptor names are literals. All input and output is through ANSI the
varying character type.

The program connects you to ORACLE using your username and password,
then prompts you for a SQL statement.  Enter legal SQL or PL/SQL 
statements using regular, not embedded, SQL syntax and terminate each 
statement with a seimcolon.  Your statement will be processed.  If it
is a query, the fetched rows are displayed.  

You can enter multiline statements.  The limit is 1023 characters.
There is a limit on the size of the variables, MAX_VAR_LEN, defined as 255.
This program processes up to 40 bind variables and 40 select-list items.
DML returning statments and user defined types are not supported with 
value semantics.

Precompile the program with mode=ansi, for example:
 
proc mode=ansi ansidyn1

Using mode=ansi will set dynamic and type_code to ansi.

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

#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <stdlib.h>
#include <sqlcpr.h>

#define MAX_OCCURENCES 40
#define MAX_VAR_LEN    255
#define MAX_NAME_LEN   31

#ifndef NULL
#define NULL  0
#endif


/* Prototypes */
#if defined(__STDC__)
  void sql_error(void);
  int oracle_connect(void);
  int get_dyn_statement(void);
  int process_input(void);
  int process_output(void);
  void help(void);
#else
  void sql_error(/*_ void _*/);
  int oracle_connect(/*_ void _*/);
  int get_dyn_statement(/* void _*/);
  int process_input(/*_ void _*/);
  int process_output(/*_ void _*/);
  void help(/*_ void _*/);
#endif

EXEC SQL INCLUDE sqlca;

char SQLSTATE[6];

/* global variables */
EXEC SQL BEGIN DECLARE SECTION;
 char    dyn_statement[1024];
 char SQLSTATE[6];
EXEC SQL END DECLARE SECTION;




/* Define a buffer to hold longjmp state info. */
jmp_buf jmp_continue;

/* A global flag for the error routine. */
int parse_flag = 0;
/* A global flag to indicate statement is a select */
int select_found;   

void main()
{

    /* Connect to the database. */
    if (oracle_connect() != 0)
        exit(1);

    EXEC SQL WHENEVER SQLERROR DO sql_error();

    /* Allocate the input and output descriptors. */
    EXEC SQL ALLOCATE DESCRIPTOR 'input_descriptor';
    EXEC SQL ALLOCATE DESCRIPTOR 'output_descriptor';

    /* Process SQL statements. */
    for (;;) 
    {
        (void) setjmp(jmp_continue);

        /* Get the statement.  Break on "exit". */
        if (get_dyn_statement() != 0)
            break;

        /* Prepare the statement and declare a cursor. */
        parse_flag = 1;     /* Set a flag for sql_error(). */
        EXEC SQL PREPARE S FROM :dyn_statement;
        parse_flag = 0;     /* Unset the flag. */

        EXEC SQL DECLARE C CURSOR FOR S;

        /* Call the function that processes the input. */
        if (process_input())
            exit(1);
 
        /* Open the cursor and execute the statement. */
        EXEC SQL OPEN C USING DESCRIPTOR 'input_descriptor';

        /* Call the function that processes the output. */
        if (process_output())
            exit(1);

        /* Close the cursor. */
        EXEC SQL CLOSE C;

    }   /* end of for(;;) statement-processing loop */


    /* Deallocate the descriptors */
    EXEC SQL DEALLOCATE DESCRIPTOR 'input_descriptor';
    EXEC SQL DEALLOCATE DESCRIPTOR 'output_descriptor';

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL COMMIT WORK;
    puts("\nHave a good day!\n");

    EXEC SQL WHENEVER SQLERROR DO sql_error();
    return;
}



int get_dyn_statement()
{
    char *cp, linebuf[256];
    int iter, plsql;

    for (plsql = 0, iter = 1; ;)
    {
        if (iter == 1)
        {
            printf("\nSQL> ");
            dyn_statement[0] = '\0';
            select_found = 0;
        }
        
        fgets(linebuf, sizeof linebuf, stdin);

        cp = strrchr(linebuf, '\n');
        if (cp && cp != linebuf)
            *cp = ' ';
        else if (cp == linebuf)
            continue;

        if ((strncmp(linebuf, "SELECT", 6) == 0) ||
            (strncmp(linebuf, "select", 6) == 0))
        {
            select_found=1;;
        }

        if ((strncmp(linebuf, "EXIT", 4) == 0) ||
            (strncmp(linebuf, "exit", 4) == 0))
        {
            return -1;
        }

        else if (linebuf[0] == '?' ||
            (strncmp(linebuf, "HELP", 4) == 0) ||
            (strncmp(linebuf, "help", 4) == 0))
        {
            help();
            iter = 1;
            continue;
        }

        if (strstr(linebuf, "BEGIN") ||
            (strstr(linebuf, "begin")))
        {
            plsql = 1;
        }

        strcat(dyn_statement, linebuf);

        if ((plsql && (cp = strrchr(dyn_statement, '/'))) ||
            (!plsql && (cp = strrchr(dyn_statement, ';'))))
        {
            *cp = '\0';
            break;
        }
        else
        {
            iter++;
            printf("%3d  ", iter);
        }
    }
    return 0;
}


int process_input()
{
    int i;
    EXEC SQL BEGIN DECLARE SECTION;
      char name[31];
      int  input_count, input_len, occurs, ANSI_varchar_type;
      char input_buf[MAX_VAR_LEN];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DESCRIBE INPUT S USING DESCRIPTOR 'input_descriptor';
    EXEC SQL GET DESCRIPTOR 'input_descriptor' :input_count = COUNT; 
       
    ANSI_varchar_type=12;
    for (i=0; i < input_count; i++)
    {
        occurs = i +1;                       /* occurence is 1 based */
        EXEC SQL GET DESCRIPTOR 'input_descriptor' 
                 VALUE :occurs :name = NAME;
        printf ("\nEnter value for input variable %*.*s:  ", 10,31, name);
        fgets(input_buf, sizeof(input_buf), stdin);
        input_len = strlen(input_buf) - 1;  /* get rid of new line */
        input_buf[input_len] = '\0';        /* null terminate */
        EXEC SQL SET DESCRIPTOR 'input_descriptor'
                 VALUE :occurs TYPE = :ANSI_varchar_type, 
                               LENGTH = :input_len,
                               DATA = :input_buf;
    }
    return(sqlca.sqlcode);
}


int process_output()
{
   int i, j;
   EXEC SQL BEGIN DECLARE SECTION;
     int output_count, occurs, type, len, col_len;
     short indi;
     char data[MAX_VAR_LEN], name[MAX_NAME_LEN];
   EXEC SQL END DECLARE SECTION;
   if (!select_found)
       return(0);   

   EXEC SQL DESCRIBE OUTPUT S USING DESCRIPTOR 'output_descriptor';
   
   EXEC SQL GET DESCRIPTOR 'output_descriptor' :output_count = COUNT;


   printf ("\n");
   type = 12;            /* ANSI VARYING character type */
   len = MAX_VAR_LEN;    /* use the max allocated length */
   for (i = 0; i < output_count; i++)
    {
        occurs = i + 1;
        EXEC SQL GET DESCRIPTOR 'output_descriptor' VALUE :occurs
                 :name = NAME;
        printf("%-*.*s ", 9,9, name);
        EXEC SQL SET DESCRIPTOR 'output_descriptor' VALUE :occurs 
                 TYPE = :type, LENGTH = :len;
    }   
    printf("\n");

    /* FETCH each row selected and print the column values. */
    EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop;

    for (;;)
    {
        EXEC SQL FETCH C INTO DESCRIPTOR 'output_descriptor';
        for (i=0; i < output_count; i++)
          {
            occurs = i + 1;
            EXEC SQL GET DESCRIPTOR 'output_descriptor' VALUE :occurs
                 :data = DATA, :indi = INDICATOR;
            if (indi == -1)       
              printf("%-*.*s ", 9,9, "NULL");
            else
              printf("%-*.*s ", 9,9, data);  /* simplified output formatting */ 
                          /* truncation will occur, but columns will line up */
          }                             
         printf ("\n");
    }
end_select_loop:
    return(0);
}



void help()
{
    puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt.");
    puts("Statements can be continued over several lines, except");
    puts("within string literals.");
    puts("Terminate a SQL statement with a semicolon.");
    puts("Terminate a PL/SQL block (which can contain embedded semicolons)");
    puts("with a slash (/).");
    puts("Typing \"exit\" (no semicolon needed) exits the program.");
    puts("You typed \"?\" or \"help\" to get this message.\n\n");
}


void sql_error()
{
    /* ORACLE error handler */
    printf("\n\nANSI sqlstate: %s: ", SQLSTATE);
    printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc);
    if (parse_flag)
        printf
        ("Parse error at character offset %d in SQL statement.\n",
           sqlca.sqlerrd[4]);

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL ROLLBACK WORK;
    longjmp(jmp_continue, 1);
}


int oracle_connect()
{
    EXEC SQL BEGIN DECLARE SECTION;
        VARCHAR  username[128];
        VARCHAR  password[32];
    EXEC SQL END DECLARE SECTION;

    printf("\nusername: ");
    fgets((char *) username.arr, sizeof username.arr, stdin);
    username.arr[strlen((char *) username.arr)-1] = '\0';
    username.len = (unsigned short)strlen((char *) username.arr);

    printf("password: ");
    fgets((char *) password.arr, sizeof password.arr, stdin);
    password.arr[strlen((char *) password.arr) - 1] = '\0';
    password.len = (unsigned short)strlen((char *) password.arr);


    EXEC SQL WHENEVER SQLERROR GOTO connect_error;

    EXEC SQL CONNECT :username IDENTIFIED BY :password;

    printf("\nConnected to ORACLE as user %s.\n", username.arr);

    return 0;

connect_error:
    fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr);
    return -1;
}

14.6.2 ansidyn2.pc

このプログラムは、ANSI動的SQLを使用したSQL文の処理方法を示します。このSQL文は実行時まで不明です。バッチ処理および参照セマンティクスのOracle拡張機能が使用されます。

このプログラムでは、自分のユーザー名とパスワードを使用してORACLEに接続した後、SQL文を入力します。埋込み型ではなく対話型のSQL構文を使用して有効なSQLまたはPL/SQL文を入力し、各文の終わりにセミコロンを付けてください。入力した文が処理されます。問合せのときはフェッチされた行が表示されます。

複数行の文を入力できます。最大1023文字まで入力できます。変数のサイズには制限があり、MAX_VAR_LENが255に定義されています。このプログラムでは、バインド変数は40、選択リスト項目も40まで処理できます。

次のように、dynamic = ansiに設定してプログラムをプリコンパイルします。

proc dynamic=ansi ansidyn2

/*******************************************************************
ANSI Dynamic Demo 2:  ANSI Dynamic SQL with reference semantics,
                           batch processing and global descriptor
                           names in host variables
                           
This program demonstates using ANSI Dynamic SQL to process SQL
statements which are not known until runtime.  It uses the Oracle
extensions for batch processing and reference semantics.

The program connects you to ORACLE using your username and password,
then prompts you for a SQL statement.  Enter legal SQL or PL/SQL 
statement using interactive, not embedded, SQL syntax, terminating the 
statement with a seimcolon.  Your statement will be processed.  If it
is a query, the fetched rows are displayed. 

If your statement has input bind variables (other than in a where clause),
the program will ask for an input array size and then allow you to enter 
that number of input values. If your statment has output, the program will
ask you for an output array size and will do array fetchng using that value.
It will also output the rows fetched in one batch together, so using a small
value for the output array size will improve the look of the output.  
For example, connected as scott/tiger, try select empno, ename from emp
with an output array size of 4;

You can enter multiline statements.  The limit is 1023 characters.
There is a limit on the size of the variables, MAX_VAR_LEN, defined as 255.
This program processes up to 40 bind variables and 40 select-list items.

Precompile with program with dynamic=ansi, for example:
 
proc dynamic=ansi ansidyn2

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

#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <stdlib.h>
#include <sqlcpr.h>


#define MAX_OCCURENCES  40
#define MAX_ARRSZ      100
#define MAX_VAR_LEN    255
#define MAX_NAME_LEN    31

#ifndef NULL
#define NULL  0
#endif


/* Prototypes */
#if defined(__STDC__)
  void sql_error(void);
  int oracle_connect(void);
  int get_dyn_statement(void);
  int process_input(void);
  int process_output(void);
  void rows_processed(void);
  void help(void);
#else
  void sql_error(/*_ void _*/);
  int oracle_connect(/*_ void _*/);
  int get_dyn_statement(/* void _*/);
  int process_input(/*_ void _*/);
  int process_output(/*_ void _*/);
  void rows_processed(/*_ void _*/);
  void help(/*_ void _*/);
#endif

EXEC SQL INCLUDE sqlca;

/* global variables */
char    dyn_statement[1024];                      /* statement variable     */
EXEC SQL VAR dyn_statement IS STRING(1024);

char  indesc[]="input_descriptor";                /* descriptor names       */
char outdesc[]="output_descriptor";
char   input[MAX_OCCURENCES][MAX_ARRSZ][MAX_VAR_LEN +1 ],    /* data areas */
      output[MAX_OCCURENCES][MAX_ARRSZ][MAX_VAR_LEN + 1];

short outindi[MAX_OCCURENCES][MAX_ARRSZ];        /* output indicators      */
short *iptr;

int   in_array_size;     /* size of input batch, that is, number of rows */
int   out_array_size;    /* size of input batch, that is, number of rows */
int   max_array_size=MAX_ARRSZ;   /* maximum arrays size used for allocates */

char *dml_commands[] = {"SELECT", "select", "INSERT", "insert",
                        "UPDATE", "update", "DELETE", "delete"};

int select_found, cursor_open = 0;

/* Define a buffer to hold longjmp state info. */
jmp_buf jmp_continue;

/* A global flag for the error routine. */
int parse_flag = 0;

void main()
{

    /* Connect to the database. */
    if (oracle_connect() != 0)
        exit(1);

    EXEC SQL WHENEVER SQLERROR DO sql_error();

    /* Allocate the input and output descriptors. */
    EXEC SQL FOR :max_array_size
             ALLOCATE DESCRIPTOR GLOBAL :indesc;
    EXEC SQL FOR :max_array_size
             ALLOCATE DESCRIPTOR GLOBAL :outdesc;

    /* Process SQL statements. */
    for (;;) 
    {
        (void) setjmp(jmp_continue);

        /* Get the statement.  Break on "exit". */
        if (get_dyn_statement() != 0)
            break;

        /* Prepare the statement and declare a cursor. */
        parse_flag = 1;     /* Set a flag for sql_error(). */
        EXEC SQL PREPARE S FROM :dyn_statement;
        parse_flag = 0;     /* Unset the flag. */

        EXEC SQL DECLARE C CURSOR FOR S;

        /* Call the function that processes the input. */
        if (process_input())
            exit(1);

        /* Open the cursor and execute the statement. */
        EXEC SQL FOR :in_array_size
            OPEN C USING DESCRIPTOR GLOBAL :indesc;
        cursor_open = 1;
 
        /* Call the function that processes the output. */
        if (process_output())
            exit(1);

        /* Tell user how many rows were processed. */
        rows_processed();

    }   /* end of for(;;) statement-processing loop */

 
    /* Close the cursor. */
    if (cursor_open)
      EXEC SQL CLOSE C;

    /* Deallocate the descriptors */
    EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL :indesc;
    EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL :outdesc;

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL COMMIT WORK RELEASE;
    puts("\nHave a good day!\n");

    EXEC SQL WHENEVER SQLERROR DO sql_error();
    return;
}



int get_dyn_statement()
{
    char *cp, linebuf[256];
    int iter, plsql;

    for (plsql = 0, iter = 1; ;)
    {
        if (iter == 1)
        {
            printf("\nSQL> ");
            dyn_statement[0] = '\0';
            select_found = 0;
        }
        
        fgets(linebuf, sizeof linebuf, stdin);

        cp = strrchr(linebuf, '\n');
        if (cp && cp != linebuf)
            *cp = ' ';
        else if (cp == linebuf)
            continue;

        if ((strncmp(linebuf, "SELECT", 6) == 0) ||
            (strncmp(linebuf, "select", 6) == 0))
        {
            select_found=1;;
        }

        if ((strncmp(linebuf, "EXIT", 4) == 0) ||
            (strncmp(linebuf, "exit", 4) == 0))
        {
            return -1;
        }

        else if (linebuf[0] == '?' ||
            (strncmp(linebuf, "HELP", 4) == 0) ||
            (strncmp(linebuf, "help", 4) == 0))
        {
            help();
            iter = 1;
            continue;
        }

        if (strstr(linebuf, "BEGIN") ||
            (strstr(linebuf, "begin")))
        {
            plsql = 1;
        }

        strcat(dyn_statement, linebuf);

        if ((plsql && (cp = strrchr(dyn_statement, '/'))) ||
            (!plsql && (cp = strrchr(dyn_statement, ';'))))
        {
            *cp = '\0';
            break;
        }
        else
        {
            iter++;
            printf("%3d  ", iter);
        }
    }
    return 0;
}


int process_input()
{
    int i, j;
    char name[31];
    int  input_count, input_len= MAX_VAR_LEN;
    int  occurs, string_type = 5;
    int  string_len;
    char arr_size[3];

    EXEC SQL DESCRIBE INPUT S USING DESCRIPTOR GLOBAL :indesc;
    EXEC SQL GET DESCRIPTOR GLOBAL :indesc :input_count = COUNT; 

    if (input_count > 0 && !select_found )
       {     /* get input array size */
          printf ("\nEnter value for input array size (max is %d) :  ", 
                           max_array_size);
        fgets(arr_size, 4, stdin);
        in_array_size = atoi(arr_size); 
       }
    else
       { 
         in_array_size = 1;
       }
    for (i=0; i < input_count; i++)
    {
        occurs = i +1;                       /* occurence is 1 based */
        EXEC SQL GET DESCRIPTOR GLOBAL :indesc 
                 VALUE :occurs :name = NAME;

        for (j=0; j < in_array_size; j++)
        {
          if (in_array_size == 1)
            printf ("\nEnter value for input variable %*.*s:  ",10,31, name);
          else 
            printf ("\nEnter %d%s value for input variable %*.*s:  ",
               j +1, ((j==0) ?  "st" :  (j==1) ? "nd" : (j==2) ? "rd" :"th"),
                      10,31, name);
          fgets(input[i][j], sizeof(input[i][j]), stdin);
          string_len = strlen(input[i][j]);
          input[i][j][string_len - 1 ] = '\0';   /* change \n to \0 */
        }
        EXEC SQL SET DESCRIPTOR GLOBAL :indesc
                 VALUE :occurs TYPE = :string_type, LENGTH = :input_len;
        EXEC SQL FOR :in_array_size
                 SET DESCRIPTOR GLOBAL :indesc
                     VALUE :occurs  REF DATA = :input[i];
    }

    return(sqlca.sqlcode);
}


int process_output()
{
   int i, j;
   int output_count, occurs;
   int type, output_len= MAX_VAR_LEN;
   char name[MAX_OCCURENCES][MAX_NAME_LEN];
   int rows_this_fetch=0, cumulative_rows=0;
   char arr_size[3];
   if (!select_found)
      return(0);   
   EXEC SQL DESCRIBE OUTPUT S USING DESCRIPTOR GLOBAL :outdesc;
   
   EXEC SQL GET DESCRIPTOR GLOBAL :outdesc :output_count = COUNT;
  
   if (output_count > 0 )
      {
        printf ("\nEnter value for output array size (max is %d) :  ", 
                       max_array_size);
        fgets(arr_size, 4, stdin);
        out_array_size = atoi(arr_size); 
      }
   if (out_array_size < 1)    /* must have at least one */
       out_array_size = 1;

   printf ("\n");
   
   for (i = 0; i < output_count; i++)
   {
      occurs = i + 1;
      EXEC SQL GET DESCRIPTOR GLOBAL :outdesc VALUE :occurs
               :type = TYPE, :name[i] = NAME;
      occurs = i + 1;                         /* occurence is one based */
      type = 5;  /* force all data to be null terminated character */
      EXEC SQL SET DESCRIPTOR GLOBAL :outdesc VALUE :occurs 
               TYPE = :type, LENGTH = :output_len;
   
      iptr = (short *)&outindi[i]; /* no mult-dimension non-char host vars */
      EXEC SQL FOR :out_array_size
               SET DESCRIPTOR GLOBAL :outdesc VALUE :occurs 
               REF DATA = :output[i], REF INDICATOR = :iptr;
   }   
   
   
   
   EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop;
   
   /* print the column headings */
   for (j=0; j < out_array_size; j++)
      for (i=0; i < output_count; i++)
         printf("%-*.*s ", 9,9, name[i]);
   printf("\n");
   
   /* FETCH each row selected and print the column values. */
   for (;;)
   {
      EXEC SQL FOR :out_array_size 
              FETCH C INTO DESCRIPTOR GLOBAL :outdesc;
      rows_this_fetch = sqlca.sqlerrd[2] - cumulative_rows;
      cumulative_rows = sqlca.sqlerrd[2];
      if (rows_this_fetch)
      for (j=0; j < out_array_size && j < rows_this_fetch; j++)
      {           /* output by columns using simplified formatting */
         for (i=0; i < output_count; i++)
           {                              
                if (outindi[i][j] == -1)       
                   printf("%-*.*s ", 9, 9, "NULL");
               else
                  printf("%-*.*s ", 9, 9, output[i][j]);  /* simplified */
                              /* output formatting may cause truncation */
                              /* but columns will line up */
           } 
      }
       printf ("\n");
   }

end_select_loop:
   /* print any unprinted rows */
   rows_this_fetch = sqlca.sqlerrd[2] - cumulative_rows;
   cumulative_rows = sqlca.sqlerrd[2];
   if (rows_this_fetch)
     for (j=0; j < out_array_size && j < rows_this_fetch; j++)
       {           /* output by columns using simplified formatting */
         for (i=0; i < output_count; i++)
           {                              
              if (outindi[i][j] == -1)       
                   printf("%-*.*s ",9, 9, "NULL");
               else
                  printf("%-*.*s ", 9, 9, output[i][j]); 
            } 
        }
   return(0);
}

void rows_processed()
{  
   int i;
   for (i = 0; i < 8; i++)
     {
       if (strncmp(dyn_statement, dml_commands[i], 6) == 0)
         {
            printf("\n\n%d row%c processed.\n", sqlca.sqlerrd[2],
                       sqlca.sqlerrd[2] == 1 ? ' ' : 's');
            break;
         }
     }
   return;
}


void help()
{
    puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt.");
    puts("Statements can be continued over several lines, except");
    puts("within string literals.");
    puts("Terminate a SQL statement with a semicolon.");
    puts("Terminate a PL/SQL block (which can contain embedded semicolons)");
    puts("with a slash (/).");
    puts("Typing \"exit\" (no semicolon needed) exits the program.");
    puts("You typed \"?\" or \"help\" to get this message.\n\n");
}


void sql_error()
{
    /* ORACLE error handler */
    printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc);
    if (parse_flag)
        printf
        ("Parse error at character offset %d in SQL statement.\n",
           sqlca.sqlerrd[4]);

    EXEC SQL WHENEVER SQLERROR CONTINUE;
    EXEC SQL ROLLBACK WORK;
    longjmp(jmp_continue, 1);
}


int oracle_connect()
{
    EXEC SQL BEGIN DECLARE SECTION;
        VARCHAR  username[128];
        VARCHAR  password[32];
    EXEC SQL END DECLARE SECTION;

    printf("\nusername: ");
    fgets((char *) username.arr, sizeof username.arr, stdin);
    username.arr[strlen((char *) username.arr)-1] = '\0';
    username.len = (unsigned short)strlen((char *) username.arr);

    printf("password: ");
    fgets((char *) password.arr, sizeof password.arr, stdin);
    password.arr[strlen((char *) password.arr) - 1] = '\0';
    password.len = (unsigned short)strlen((char *) password.arr);


    EXEC SQL WHENEVER SQLERROR GOTO connect_error;

    EXEC SQL CONNECT :username IDENTIFIED BY :password;

    printf("\nConnected to ORACLE as user %s.\n", username.arr);

    return 0;

connect_error:
    fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr);
    return -1;
}