記述子の使用¶
動的SQLを使用すると、クライアントアプリケーションは、実行時に構成されるSQLステートメントを実行できます。これは、クライアントアプリケーションを記述しているときに内容や形式がわからない場合に便利です。ECPGPlusでは、識別子(テーブル名、列名、インデックス名など)の代わりにホスト変数を使用することはできません。代わりに、動的SQLステートメントを使用して、情報を含む文字列を作成し、その文字列を実行する必要があります。文字列は、クライアントとサーバーの間で*descriptor* の形式で渡されます。記述子は、データとデータの形状に関する情報の両方を含むデータ構造です。
クライアントアプリケーションは、 GET DESCRIPTOR ステートメントを使用して記述子から情報を取得する必要があります。次の手順では、動的SQLを使用したクライアントアプリケーションの基本的なフローについて説明します。
ALLOCATE DESCRIPTORステートメントを使用して、結果セット(選択リスト)の記述子を割り当てます。ALLOCATE DESCRIPTORステートメントを使用して、入力パラメーター(バインド変数)の記述子を割り当てます。SQLステートメントのテキストを取得、アセンブル、または計算します。
PREPAREステートメントを使用して、SQLステートメントを解析および構文チェックします。DESCRIBEステートメントを使用して、選択リストを選択リスト記述子に記述します。DESCRIBEステートメントを使用して、バインド変数記述子への入力パラメーターを記述します。各入力パラメーターの値をユーザーに要求します(必要な場合)。
SET DESCRIPTORステートメントを使用して、値を記述子に割り当てます。DECLARE CURSORステートメントを使用して、ステートメントのカーソルを定義します。OPEN CURSORステートメントを使用して、ステートメントのカーソルを開きます。FETCHステートメントを使用して、カーソルから各行をフェッチし、選択リスト記述子に各行を保存します。GET DESCRIPTORコマンドを使用して、選択リスト記述子に問い合わせて、現在の行の各列の値を見つけます。CLOSE CURSORステートメントを使用して、カーソルを閉じ、カーソルリソースを解放します。
記述子には、以下の表にリストされている属性が含まれる場合があります。
Field |
Type |
Attribute Description |
|---|---|---|
カーディナリティ |
整数 |
結果セットの行数。 |
データ |
なし |
データ値。 |
DATETIME_INTERVAL_CODE |
整数 |
TYPEが9の場合:
|
DATETIME_INTERVAL_PRECISION |
整数 |
未使用。 |
指標 |
整数 |
NULLまたは切り捨てられた値を示します。 |
KEY_MEMBER |
整数 |
未使用(FALSEを返します)。 |
長さ |
整数 |
データの長さ(サーバーに保存されている)。 |
NAME |
ひも |
データが存在する列の名前。 |
NULLABLE |
整数 |
未使用(TRUEを返します)。 |
OCTET_LENGTH |
整数 |
サーバーに保存されているデータの長さ(バイト単位)。 |
精度 |
整数 |
データ精度(データが数値型の場合)。 |
RETURNED_LENGTH |
整数 |
データ項目の実際の長さ。 |
RETURNED_OCTET_LENGTH |
整数 |
データ項目の実際の長さ。 |
スケール |
整数 |
データスケール(データが数値型の場合)。 |
TYPE |
整数 |
列のデータ型を表す数値コード:
|
例-記述子を使用してデータを返す¶
次の簡単なアプリケーションは、エンドユーザーが入力したSQLステートメントを実行します。コードサンプルは以下を示します。
SQL記述子を使用して
SELECTステートメントを実行する方法。ステートメントによって返されたデータとメタデータを見つける方法。
アプリケーションは、エンドユーザーからのSQLステートメントを受け入れ、ステートメントをテストして SELECT キーワードが含まれているかどうかを確認し、ステートメントを実行します。
アプリケーションを呼び出すとき、エンドユーザーは、SQLステートメントが実行されるデータベースの名前と、クエリのテキストを含む文字列を提供する必要があります。
たとえば、ユーザーは次のコマンドでサンプルを呼び出すことができます。
./exec_stmt edb "SELECT * FROM emp"
/************************************************************
/* exec_stmt.pgc
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql3types.h>
#include <sqlca.h>
EXEC SQL WHENEVER SQLERROR SQLPRINT;
static void print_meta_data( char * desc_name );
char *md1 = "col field data ret";
char *md2 = "num name type len";
char *md3 = "--- -------------------- ----------------- ---";
int main( int argc, char *argv[] )
{
EXEC SQL BEGIN DECLARE SECTION;
char *db = argv[1];
char *stmt = argv[2];
int col_count;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO :db;
EXEC SQL ALLOCATE DESCRIPTOR parse_desc;
EXEC SQL PREPARE query FROM :stmt;
EXEC SQL DESCRIBE query INTO SQL DESCRIPTOR parse_desc;
EXEC SQL GET DESCRIPTOR 'parse_desc' :col_count = COUNT;
if( col_count == 0 )
{
EXEC SQL EXECUTE IMMEDIATE :stmt;
if( sqlca.sqlcode >= 0 )
EXEC SQL COMMIT;
}
else
{
int row;
EXEC SQL ALLOCATE DESCRIPTOR row_desc;
EXEC SQL DECLARE my_cursor CURSOR FOR query;
EXEC SQL OPEN my_cursor;
for( row = 0; ; row++ )
{
EXEC SQL BEGIN DECLARE SECTION;
int col;
EXEC SQL END DECLARE SECTION;
EXEC SQL FETCH IN my_cursor
INTO SQL DESCRIPTOR row_desc;
if( sqlca.sqlcode != 0 )
break;
if( row == 0 )
print_meta_data( "row_desc" );
printf("[RECORD %d]\n", row+1);
for( col = 1; col <= col_count; col++ )
{
EXEC SQL BEGIN DECLARE SECTION;
short ind;
varchar val[40+1];
varchar name[20+1];
EXEC SQL END DECLARE SECTION;
EXEC SQL GET DESCRIPTOR 'row_desc'
VALUE :col
:val = DATA, :ind = INDICATOR, :name = NAME;
if( ind == -1 )
printf( " %-20s : <null>\n", name.arr );
else if( ind > 0 )
printf( " %-20s : <truncated>\n", name.arr );
else
printf( " %-20s : %s\n", name.arr, val.arr );
}
printf( "\n" );
}
printf( "%d rows\n", row );
}
exit( 0 );
}
static void print_meta_data( char *desc_name )
{
EXEC SQL BEGIN DECLARE SECTION;
char *desc = desc_name;
int col_count;
int col;
EXEC SQL END DECLARE SECTION;
static char *types[] =
{
"unused ",
"CHARACTER ",
"NUMERIC ",
"DECIMAL ",
"INTEGER ",
"SMALLINT ",
"FLOAT ",
"REAL ",
"DOUBLE ",
"DATE_TIME ",
"INTERVAL ",
"unused ",
"CHARACTER_VARYING",
"ENUMERATED ",
"BIT ",
"BIT_VARYING ",
"BOOLEAN ",
"abstract "
};
EXEC SQL GET DESCRIPTOR :desc :col_count = count;
printf( "%s\n", md1 );
printf( "%s\n", md2 );
printf( "%s\n", md3 );
for( col = 1; col <= col_count; col++ )
{
EXEC SQL BEGIN DECLARE SECTION;
int type;
int ret_len;
varchar name[21];
EXEC SQL END DECLARE SECTION;
char *type_name;
EXEC SQL GET DESCRIPTOR :desc
VALUE :col
:name = NAME,
:type = TYPE,
:ret_len = RETURNED_OCTET_LENGTH;
if( type > 0 && type < SQL3_abstract )
type_name = types[type];
else
type_name = "unknown";
printf( "%02d: %-20s %-17s %04d\n",
col, name.arr, type_name, ret_len );
}
printf( "\n" );
}
/************************************************************
コードサンプルは、Cの stdio および stdlib ライブラリ、SQLデータ型シンボル、および SQLCA (SQLコミュニケーションエリア)構造のプロトタイプと型定義を含めることから始まります。
#include <stdio.h>
#include <stdlib.h>
#include <sql3types.h>
#include <sqlca.h>
サンプルは最小限のエラー処理を提供します。アプリケーションでSQLエラーが発生すると、エラーメッセージが画面に出力されます。
EXEC SQL WHENEVER SQLERROR SQLPRINT;
アプリケーションには、記述子で見つかったメタデータを出力する print_meta_data() という名前の関数の前方宣言が含まれています。
static void print_meta_data( char * desc_name );
次のコードは、アプリケーションがメタデータを印刷するときに使用する列ヘッダー情報を指定します。
char *md1 = "col field data ret";
char *md2 = "num name type len";
char *md3 = "--- -------------------- ----------------- ---";
int main( int argc, char *argv[] )
{
次の宣言セクションでは、アプリケーションが接続するデータベースの名前、SQLステートメントの内容、および結果セット(ある場合)の列数を保持するホスト変数を含むホスト変数を識別します。
EXEC SQL BEGIN DECLARE SECTION;
char *db = argv[1];
char *stmt = argv[2];
int col_count;
EXEC SQL END DECLARE SECTION;
アプリケーションはデータベースに接続します(デフォルトの資格情報を使用):
EXEC SQL CONNECT TO :db;
次に、アプリケーションはSQL記述子を割り当てて、ステートメントのメタデータを保持します。
EXEC SQL ALLOCATE DESCRIPTOR parse_desc;
アプリケーションは PREPARE ステートメントを使用して、ユーザーから提供された文字列を構文チェックします。
EXEC SQL PREPARE query FROM :stmt;
クエリのメタデータをSQL記述子に移動するための DESCRIBE ステートメント。
EXEC SQL DESCRIBE query INTO SQL DESCRIPTOR parse_desc;
次に、アプリケーションは記述子に問い合わせて結果セット内の列数を検出し、それをホスト変数 col_count に格納します。
EXEC SQL GET DESCRIPTOR parse_desc :col_count = COUNT;
列数がゼロの場合、エンドユーザーは SELECT ステートメントを入力しませんでした。アプリケーションは EXECUTE IMMEDIATE ステートメントを使用して、ステートメントの内容を処理します。
if( col_count == 0 )
{
EXEC SQL EXECUTE IMMEDIATE :stmt;
ステートメントが正常に実行されると、アプリケーションは COMMIT を実行します。
if( sqlca.sqlcode >= 0 )
EXEC SQL COMMIT;
}
else
{
ユーザーが入力したステートメントが SELECT ステートメント(列数がゼロ以外であることがわかっている)である場合、アプリケーションは row という名前の変数を宣言します。
int row;
次に、アプリケーションは、結果セット内の特定の行の説明と値を保持する別の記述子を割り当てます。
EXEC SQL ALLOCATE DESCRIPTOR row_desc;
アプリケーションは、準備されたステートメントのカーソルを宣言して開きます。
EXEC SQL DECLARE my_cursor CURSOR FOR query;
EXEC SQL OPEN my_cursor;
結果セットの行をループします。
for( row = 0; ; row++ )
{
EXEC SQL BEGIN DECLARE SECTION;
int col;
EXEC SQL END DECLARE SECTION;
次に、 FETCH を使用してカーソルから次の行を記述子に取得します。
EXEC SQL FETCH IN my_cursor INTO SQL DESCRIPTOR row_desc;
アプリケーションは、 FETCH が失敗しなかったことを確認します。 FETCH が失敗すると、アプリケーションは結果セットの最後に到達し、ループを中断します。
if( sqlca.sqlcode != 0 )
break;
アプリケーションは、これがカーソルの最初の行であるかどうかを確認します。そうである場合、アプリケーションは行のメタデータを出力します。
if( row == 0 )
print_meta_data( "row_desc" );
次に、行番号を含むレコードヘッダーを出力します。
printf("[RECORD %d]\n", row+1);
次に、行の各列をループします。
for( col = 1; col <= col_count; col++ )
{
EXEC SQL BEGIN DECLARE SECTION;
short ind;
varchar val[40+1];
varchar name[20+1];
EXEC SQL END DECLARE SECTION;
アプリケーションは、行記述子 (row_desc) に問い合わせて、列値 (:val) 、ヌルインジケータ (:ind) 、および列名 (:name) を上記で宣言したホスト変数にコピーします。コンマ区切りリストを使用して、記述子から複数のアイテムを取得できることに注意してください。
EXEC SQL GET DESCRIPTOR row_desc
VALUE :col
:val = DATA, :ind = INDICATOR, :name = NAME;
nullインジケータ (ind) が負の場合、列の値は NULL です。nullインジケーターが 0 より大きい場合、列の値が長すぎてvalホスト変数に収まらないため( <truncated>) を出力します。それ以外の場合、nullインジケーターは 0 ( NOT NULL を意味する)であるため、出力しますいずれの場合も、値(または <null> または <truncated> )の前に列の名前を付けます。
if( ind == -1 )
printf( " %-20s : <null>\n", name.arr );
else if( ind > 0 )
printf( " %-20s : <truncated>\n", name.arr );
else
printf( " %-20s : %s\n", name.arr, val.arr );
}
printf( "\n" );
}
ループが終了すると、アプリケーションはフェッチされた行数を出力して終了します。
printf( "%d rows\n", row );
}
exit( 0 );
}
print_meta_data() 関数は、記述子からメタデータを抽出し、各列の名前、データ型、および長さを出力します。
static void print_meta_data( char *desc_name )
{
アプリケーションはホスト変数を宣言します:
EXEC SQL BEGIN DECLARE SECTION;
char *desc = desc_name;
int col_count;
int col;
EXEC SQL END DECLARE SECTION;
次に、アプリケーションは、データ型値 (numeric) をデータ型名にマッピングする文字列の配列を定義します。記述子で見つかった数値を使用して、この配列にインデックスを付けます。たとえば、特定の列のタイプが 2 であることが判明した場合、 types[2] と記述することでそのタイプの名前 (NUMERIC) を見つけることができます。
static char *types[] =
{
"unused ",
"CHARACTER ",
"NUMERIC ",
"DECIMAL ",
"INTEGER ",
"SMALLINT ",
"FLOAT ",
"REAL ",
"DOUBLE ",
"DATE_TIME ",
"INTERVAL ",
"unused ",
"CHARACTER_VARYING",
"ENUMERATED ",
"BIT ",
"BIT_VARYING ",
"BOOLEAN ",
"abstract "
};
アプリケーションは、記述子から列カウントを取得します。プログラムは、記述子の名前を含むホスト変数 (desc) を使用して記述子を参照することに注意してください。ほとんどのシナリオでは、識別子を使用して記述子を参照しますが、この場合、呼び出し元が記述子名を提供したため、ホスト変数を使用して記述子を参照できます。
EXEC SQL GET DESCRIPTOR :desc :col_count = count;
アプリケーションは列ヘッダーを印刷します(このアプリケーションの冒頭で定義されています):
printf( "%s\n", md1 );
printf( "%s\n", md2 );
printf( "%s\n", md3 );
次に、記述子にある各列をループし、各列の名前、タイプ、および長さを出力します。
for( col = 1; col <= col_count; col++ )
{
EXEC SQL BEGIN DECLARE SECTION;
int type;
int ret_len;
varchar name[21];
EXEC SQL END DECLARE SECTION;
char *type_name;
現在の列の名前、タイプコード、および長さを取得します。
EXEC SQL GET DESCRIPTOR :desc
VALUE :col
:name = NAME,
:type = TYPE,
:ret_len = RETURNED_OCTET_LENGTH;
数値型コードが「既知の」型コード(つまり、 types[] 配列にある型コード)と一致する場合、対応する型の名前に type_name を設定します。それ以外の場合は、 type_name を /"unknown/" に設定します。
if( type > 0 && type < SQL3_abstract )
type_name = types[type];
else
type_name = "unknown";
列番号、名前、タイプ名、および長さを出力します。
printf( "%02d: %-20s %-17s %04d\n",
col, name.arr, type_name, ret_len );
}
printf( "\n" );
}
次のコマンドでサンプルアプリケーションを呼び出す場合:
./exec_stmt test "SELECT * FROM emp WHERE empno IN(7902, 7934)"
アプリケーションは以下を返します。
col field data ret
num name type len
--- -------------------- ----------------- ---
01: empno NUMERIC 0004
02: ename CHARACTER_VARYING 0004
03: job CHARACTER_VARYING 0007
04: mgr NUMERIC 0004
05: hiredate DATE_TIME 0018
06: sal NUMERIC 0007
07: comm NUMERIC 0000
08: deptno NUMERIC 0002
[RECORD 1]
empno : 7902
ename : FORD
job : ANALYST
mgr : 7566
hiredate : 03-DEC-81 00:00:00
sal : 3000.00
comm : <null>
deptno : 20
[RECORD 2]
empno : 7934
ename : MILLER
job : CLERK
mgr : 7782
hiredate : 23-JAN-82 00:00:00
sal : 1300.00
comm : <null>
deptno : 10
2 rows