記述子の使用

動的SQLを使用すると、クライアントアプリケーションは、実行時に構成されるSQLステートメントを実行できます。これは、クライアントアプリケーションを記述しているときに内容や形式がわからない場合に便利です。ECPGPlusでは、識別子(テーブル名、列名、インデックス名など)の代わりにホスト変数を使用することはできません。代わりに、動的SQLステートメントを使用して、情報を含む文字列を作成し、その文字列を実行する必要があります。文字列は、クライアントとサーバーの間で*descriptor* の形式で渡されます。記述子は、データとデータの形状に関する情報の両方を含むデータ構造です。

クライアントアプリケーションは、 GET DESCRIPTOR ステートメントを使用して記述子から情報を取得する必要があります。次の手順では、動的SQLを使用したクライアントアプリケーションの基本的なフローについて説明します。

  1. ALLOCATE DESCRIPTOR ステートメントを使用して、結果セット(選択リスト)の記述子を割り当てます。

  2. ALLOCATE DESCRIPTOR ステートメントを使用して、入力パラメーター(バインド変数)の記述子を割り当てます。

  3. SQLステートメントのテキストを取得、アセンブル、または計算します。

  4. PREPARE ステートメントを使用して、SQLステートメントを解析および構文チェックします。

  5. DESCRIBE ステートメントを使用して、選択リストを選択リスト記述子に記述します。

  6. DESCRIBE ステートメントを使用して、バインド変数記述子への入力パラメーターを記述します。

  7. 各入力パラメーターの値をユーザーに要求します(必要な場合)。 SET DESCRIPTOR ステートメントを使用して、値を記述子に割り当てます。

  8. DECLARE CURSOR ステートメントを使用して、ステートメントのカーソルを定義します。

  9. OPEN CURSOR ステートメントを使用して、ステートメントのカーソルを開きます。

  10. FETCH ステートメントを使用して、カーソルから各行をフェッチし、選択リスト記述子に各行を保存します。

  11. GET DESCRIPTOR コマンドを使用して、選択リスト記述子に問い合わせて、現在の行の各列の値を見つけます。

  12. CLOSE CURSOR ステートメントを使用して、カーソルを閉じ、カーソルリソースを解放します。

記述子には、以下の表にリストされている属性が含まれる場合があります。

Field

Type

Attribute Description

カーディナリティ

整数

結果セットの行数。

データ

なし

データ値。

DATETIME_INTERVAL_CODE

整数

TYPEが9の場合:

1-日付

2-時間

3-タイムスタンプ

4-タイムゾーンでの時間

5-タイムゾーン付きタイムスタンプ

DATETIME_INTERVAL_PRECISION

整数

未使用。

指標

整数

NULLまたは切り捨てられた値を示します。

KEY_MEMBER

整数

未使用(FALSEを返します)。

長さ

整数

データの長さ(サーバーに保存されている)。

NAME

ひも

データが存在する列の名前。

NULLABLE

整数

未使用(TRUEを返します)。

OCTET_LENGTH

整数

サーバーに保存されているデータの長さ(バイト単位)。

精度

整数

データ精度(データが数値型の場合)。

RETURNED_LENGTH

整数

データ項目の実際の長さ。

RETURNED_OCTET_LENGTH

整数

データ項目の実際の長さ。

スケール

整数

データスケール(データが数値型の場合)。

TYPE

整数

列のデータ型を表す数値コード:

1-SQL3_CHARACTER

2-SQL3_NUMERIC

3-SQL3_DECIMAL

4-SQL3_INTEGER

5-SQL3_SMALLINT

6-SQL3_FLOAT

7-SQL3_REAL

8-SQL3_DOUBLE_PRECISION

9-SQL3_DATE_TIME_TIMESTAMP

10-SQL3_INTERVAL

12-SQL3_CHARACTER_VARYING

13-SQL3_ENUMERATED

14-SQL3_BIT

15-SQL3_BIT_VARYING

16-SQL3_BOOLEAN

例-記述子を使用してデータを返す

次の簡単なアプリケーションは、エンドユーザーが入力した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