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

戻る
戻る
 
次へ
次へ
 

14 ANSI動的SQL

この章では、OracleによるANSI動的SQL(SQL標準動的SQLとも呼ばれます)の実装について説明します。ANSI動的SQLは新しい方法4のアプリケーションで使用されます。これは、旧バージョンのOracle動的SQL方法4の拡張版です。詳細は、第13章を参照してください。

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

ANSI動的SQLでは、記述子はOracleによって内部的に保持されます。一方、旧バージョンのOracle動的SQL方法4では、記述子はユーザーによりPro*C/C++プログラムで定義されます。どちらの場合も、方法4ではPro*C/C++プログラムを使用してホスト変数を含む動的SQL文を受け取ったり作成できます。含まれるホスト変数の個数は様々です。

この章の項目は、次のとおりです。

ANSI動的SQLの基本

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

SELECT ename, empno FROM emp WHERE deptno = :deptno_data 

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

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

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

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

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

ANSI SQL文の概要

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

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

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

グローバル記述子は、プログラム内のどのモジュールでも使用できます。ローカル記述子には、割り当てられたファイル内でのみアクセスできます。デフォルト値は、LOCALです。

記述子名の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文に入力値および出力値がある場合は、記述子を2つ割り当てる必要があります。1つは入力用に、もう1つは出力用に割り当てます。

SELECT ename, empno FROM emp ;

この例のように、入力値がない場合には、入力記述子は必要ありません。

INSERTS、UPDATES、DELETESおよびSELECT文の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「ANSI SQLデータ型」の値を使用して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



関連項目

Oracle型コードについては、図15-1「Oracle外部データ型とデータ型コード」を参照してください。

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

インジケータ、精度および位取りなど、他の入力値も設定できます。


関連項目

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

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にする必要があります。


関連項目

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

サンプル・コード

次の例で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文に様々なフェッチ方向を使用して、結果セットにアクセスします。

Oracle拡張機能

次の拡張機能について説明します。

参照セマンティクス

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文によっても変更されません。DESCRIBE文とそれに続くGET文を使用して、データ行をフェッチする前に列の最大幅を調べてください。

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

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

関連項目

違いの詳細は、「SET DESCRIPTOR」を参照してください。

配列を使用したバルク操作

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

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

EXEC SQL FOR 100 ALLOCATE DESCRIPTOR 'out' ;

または

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

FOR句は、記述子にアクセスする後続の文で使用されます。出力記述子でのFETCH文の配列サイズは、ALLOCATE文で指定した配列サイズ以下にする必要があります。

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

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

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 ;

同様に、入力に使用する記述子を使用して、行の集まりなどを挿入する場合、EXECUTEまたはOPEN文には、ALLOCATE文で使用するサイズ以下の配列サイズを指定する必要があります。値セマンティクスと参照セマンティクス両方のSET文で、EXECUTE文と同じサイズの配列を使用する必要があります。このSET文を使用してDATA、INDICATORまたはRETURNED_LENGTHにアクセスします。

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

次のサンプル・コードでは、出力記述子がないバルク操作について説明します。このサンプル・コードでは出力は行われず、emp表への挿入操作のみが実行されます。COUNTの値は2です。INSERT文にはename_arrempno_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

関連項目


構造体配列のサポート

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

構造体の配列は、ANSI動的SQLによってサポートされていますが、旧バージョンのOracle動的SQLではサポートされていません。

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

独自に定義したオブジェクト型では、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文を使用して自分で設定を行う必要があります。

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

マクロ・オプションのMODEを使用すると、ANSIと互換性のある特性の設定や、多くの機能の制御ができます。このオプションではANSIまたはORACLEの値を使用できます。個々の機能に対しては、マイクロ・オプションを使用します。このオプションはMODE設定を上書きします。「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()を使用する必要があります。「SQLLIBパブリック関数の新しい名前」を参照してください。

記述子の破壊。

DEALLOCATE文が使用可能です。

関数SQLLDAFree()が使用可能です。「SQLLIBパブリック関数の新しい名前」を参照してください。

データの取得。

FETCHとGET文の両方が使用可能です。

FETCH文のみを使用する必要があります。

入力データの設定。

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

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

記述子の表現。

引用符で囲んだリテラルまたは記述子名を含むホスト識別子。

SQLDAへのポインタであるホスト変数。

使用可能なデータ型。

BIT以外のすべてのANSI型およびすべてのOracle型。

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


マイクロ・オプションTYPE_CODEは、プリコンパイラによりマクロ・オプションMODEと同じ設定にされます。TYPE_CODEがANSIに等しくなるのは、DYNAMICがANSIに等しい場合のみです。

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

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

機能 TYPE_CODE = ANSI TYPE_CODE = ORACLE

動的SQLへのデータ型コード番号の入力および戻り値。

ANSI型が存在する場合はANSIコードを使用します。それ以外の場合は、負のOracleコード番号を使用します。

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

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

DYNAMICの設定に関係なく使用可能です。


動的SQL文の完全な構文


関連項目

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

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 ;

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 ;

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には、固定幅の各国語キャラクタ・セットを持つフィールドの列の長さを表す文字数が含まれます。他の文字列はバイト数で表されます。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 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 ; 

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_name1

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


関連項目:

「GET DESCRIPTOR」

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

記述子項目名 意味

TYPE

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

LENGTH

列データの最大長。

INDICATOR

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

DATA

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

CHARACTER_SET_NAME

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

TYPE

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



関連項目


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拡張機能です。REFキーワードは、DATA、INDICATORおよびRETURNED_LENGTHの記述子項目名の前にのみ使用します。REFキーワードの使用時には、GET文を使用する必要はありません。複合データ型(オブジェクト型、コレクション型、構造体の配列およびDML RETURNING句)はすべて、SET DESCRIPTORのREF形式を必要とします。


関連項目

「DML RETURNING句」

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

REFの使用時には、対応付けられたホスト変数自体がSETで使用されます。この場合、GETは必要ありません。値ではなくREFを使用している場合にかぎり、RETURNED_LENGTHを設定できます。

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

NCHARホスト入力値用にNATIONAL_CHARフィールドを2に設定します。

古いSQLでNCAHRホスト入力値用に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 ;
...

関連項目

バルク配列については、「配列を使用したバルク操作」を参照してください。

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 ;

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

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 ;

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句が実装されます。


関連項目

「DML RETURNING句」

EXEC SQL EXECUTE S1 USING SQL DESCRIPTOR GLOBAL :binddes ;

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

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 ;

DYNAMIC DECLARE CURSORの使用

用途

問合せ文に対応付けられたカーソルを宣言します。これは汎用DECLARE CURSOR文の形式です。

構文

EXEC SQL DECLARE cursor_name CURSOR FOR statement_id;

変数

cursor_name

カーソル変数(ホスト変数ではなくSQL識別子)。

statement_id

未宣言のSQL識別子。

EXEC SQL DECLARE C1 CURSOR FOR S1 ; 

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句がサポートされています。


関連項目

「DML RETURNING句」

EXEC SQL OPEN C1 USING SQL DESCRIPTOR :binddes ;

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

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 ;

動的カーソルのCLOSE

用途

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

構文

EXEC SQL CLOSE cursor ;

変数

cursor

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

EXEC SQL CLOSE C1 ;

旧バージョンのOracle動的方法4との相違点

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

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

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

  • 新しい埋込みSQL文(ALLOCATE DESCRIPTOR、DEALLOCATE DESCRIPTOR、DESCRIBE、GET DESCRIPTORおよびSET DESCRIPTOR)の導入。

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

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

  • 記述子名は、引用符で囲んだ識別子または前にコロンが付いたホスト変数のいずれかにする必要があります。

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

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

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

制限

ANSI動的SQLに対する制限は、次のとおりです。

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

  • プリコンパイラ・オプションDYNAMICをANSIに設定する必要があります。DYNAMICがANSIに設定されている場合にかぎり、プリコンパイラ・オプションTYPE_CODEをANSIに設定できます。

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

サンプル・プログラム

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

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

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