ホスト変数の使用

PostgreSQL 9.1.1文書
前のページ | 巻戻し | 第 33章ECPG - C言語による埋め込みSQL | 早送り | 次のページ

# 33.4. ホスト変数の使用

項33.3では、埋め込みSQLプログラムでどのようにSQL文を実行するのかについて説明しました。 このSQL文の中には固定値しか使用しないものや、ユーザが指定する値をSQL文の中に挿入する手段を提供しないもの、問い合わせが返す値をプログラムで処理する手段を提供しないものがありました。 この種のSQL文は実際のアプリケーションでは役に立ちません。 本節では、ホスト変数という単純な機構を使用した、Cプログラムと埋め込みSQL文との間でデータをやり取りする方法を詳細に説明します。 埋め込みSQLプログラムでは、SQL文をホスト言語となるCプログラムコードにおけるゲストguestsとみなします。 したがって、Cプログラムの変数はホスト変数と呼ばれます。

PostgreSQLバックエンドとECPGアプリケーションの間で値をやり取りするその他の方法は、 項33.7 で説明されているSQLデスクリプタを使う方法です。

## 33.4.1. 概要

埋め込みSQLにおけるCプログラムとSQL文との間でのデータのやり取りは特に単純です。 値に適切な引用符を付与するといった、様々な複雑な処理を伴う、プログラムにデータを文中に貼り付けさせるという方法はなく、単にSQL文の中に、先頭にコロンを付けたC変数名を書くだけです。 以下に例を示します。

    EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);

このSQL文は、`v1`と`v2`という2つのC変数を参照し、また、通常のSQL文字列リテラルも使用しています。 これは、使用できるデータの種類は1つだけという制限がないことを表しています。

SQL文内にCの変数を挿入するこの様式は、SQL文で値式が想定されている所であればどこでも動作します。

## 33.4.2. 宣言セクション

例えば問い合わせ内のパラメータとして、プログラムからデータベースへデータを渡す、もしくは、データベースからプログラムへデータを渡すためには、このようなデータを含むように意図されたC変数を、埋め込みSQLプリプロセッサが管理できるように、特殊な印のついたセクションで宣言する必要があります。

このセクションは以下で始まります。

    EXEC SQL BEGIN DECLARE SECTION;

そして、以下で終わります。

    EXEC SQL END DECLARE SECTION;

この行の間は、以下のような通常のC変数宣言でなければなりません。

    int   x = 4;
    char  foo[16], bar[16];

見てわかるとおり、省略可能ですが、変数に初期値を代入することができます。 変数のスコープはプログラム内の宣言セクションの場所により決まります。 また、以下のような暗黙的に宣言セクションを生成する構文を使って変数を宣言することもできます。

    EXEC SQL int i = 4;

プログラム内に複数の宣言セクションを持たせることができます。

また、宣言は普通のC変数としてそのまま出力ファイルに出力されます。 ですので、これらを再度宣言する必要はありません。 通常、SQLコマンドで使用する予定がない変数はこの特別なセクションの外側で宣言されます。

構造体やunionの定義もまた、`DECLARE`セクションの内側で表す必要があります。 さもないと、プリプロセッサはその定義が不明であるために、これらの型を扱うことができません。

## 33.4.3. クエリ実行結果の受け取り

ここまでで、プログラムで生成したデータをSQLコマンドに渡すことができるようになりました。 しかし、どのように問い合わせの結果を取り出すのでしょうか？ この目的のために、埋め込みSQLでは、通常の`SELECT`と`FETCH`を派生した、特殊なコマンドを提供しています。 これらのコマンドは特別な`INTO`句を持ち、ここで返された値をどのホスト変数に格納すればよいかを指定します。 `SELECT` は単一行を返却するクエリに使用され、`FETCH` は複数の業を返却するクエリにおいてカーソルととともに使用されます。

以下にサンプルを示します。

    /*
     * assume this table:
     * CREATE TABLE test1 (a int, b varchar(50));
     */

    EXEC SQL BEGIN DECLARE SECTION;
    int v1;
    VARCHAR v2;
    EXEC SQL END DECLARE SECTION;

     ...

    EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;

`INTO`句が選択リストと`FROM`句の間に現れます。 選択リスト内の要素数と`INTO`直後のリスト（目的リストとも呼ばれます）の要素数は等しくなければなりません。

以下に`FETCH`コマンドの使用例を示します。

    EXEC SQL BEGIN DECLARE SECTION;
    int v1;
    VARCHAR v2;
    EXEC SQL END DECLARE SECTION;

     ...

    EXEC SQL DECLARE foo CURSOR FOR SELECT a, b FROM test;

     ...

    do
    {
        ...
        EXEC SQL FETCH NEXT FROM foo INTO :v1, :v2;
        ...
    } while (...);

ここでは、`INTO`句が通常のすべての句の後ろに現れています。

これらの手法は両方とも、一度に1行のみを取り出すことができます。 複数の行を含む可能性がある結果集合を処理する必要がある場合は、2番目の例で示したようにカーソルを使用する必要があります。

## 33.4.4. データ型の対応

ECPGアプリケーションがPostgreSQLバックエンドとCアプリケーションの間で値をやり取りする際、例えばサーバからクエリの結果を受け取る、または入力パラメータとともにSQL文を実行する場合、それらの値はPostgreSQLのデータ型とホスト言語の変数の型（具体的にはC言語のデータ型）の間で変換される必要があります。 ECPGの重要な点のひとつは、ほとんどの場合においてECPGがこれらを自動的に扱うということです。

この点において、2つのデータ型があります: いくつかの単純なPostgreSQLのデータ型、`integer` や `text` などは、アプリケーションから直接読んだり書いたりすることができます。 その他のPostgreSQLのデータ型、`timestamp` や `numeric` などは、特別なライブラリ関数によってしかアクセスすることができません; 項33.4.4.2 を参照してください。

表33-1 には、PostgreSQLのどのデータ型がC言語のデータ型に対応するかを示されています。 与えられたPostgreSQLのデータ型へ値を書き込みまたは読み込みしたい場合には、対応するC言語のデータ型の変数を宣言セクションにおいて宣言しなければなりません。

表 33-1. PostgreSQLデータ型とC言語変数型の対応

PostgreSQL data type | Host variable type
--- | ---
`smallint` | `short`
`integer` | `int`
`bigint` | `long long int`
`decimal` | `decimal`[a]
`numeric` | `numeric`[a]
`real` | `float`
`double precision` | `double`
`serial` | `int`
`bigserial` | `long long int`
`oid` | `unsigned int`
`character(`n`)`, `varchar(`n`)`, `text` | `char[`n`+1]`, `VARCHAR[`n`+1]`[b]
`name` | `char[NAMEDATALEN]`
`timestamp` | `timestamp`[a]
`interval` | `interval`[a]
`date` | `date`[a]
`boolean` | `bool`[c]
注意: a. この型は特別なライブラリ関数を通してのみアクセスできます; 項33.4.4.2 を参照。 b. `ecpglib.h` で宣言。 c. ネイティブでなければ `ecpglib.h` で宣言。

### 33.4.4.1. 文字列の処理

`varchar` and `text`のような文字列のデータ型を扱うため、ホスト変数を宣言するための2つの方法があります。

ひとつは `char` の配列 `char[]` を使うことで、C言語において文字列データを扱うもっとも一般的な方法です。

    EXEC SQL BEGIN DECLARE SECTION;
        char str[50];
    EXEC SQL END DECLARE SECTION;

文字列の長さについて、自分自身で気を付けておく必要があります。 上記のホスト変数を49文字以上の文字列を返すクエリのターゲット変数として使った場合、バッファオーバーフローが発生します。

その他の方法は、ECPGによって提供される特殊なデータ型 `VARCHAR` を使う方法です。 `VARCHAR` の配列の定義は、すべての変数が名前の付いた `struct` に変換されます。 以下のような宣言は:

    VARCHAR var[180];

次のように変換されます:

    struct varchar_var { int len; char arr[180]; } var;

メンバー変数 `arr` は終端のゼロの1バイトを含む文字列を保持します。 よって、文字列を `VARCHAR` ホスト変数に格納する場合には、ホスト変数はゼロ終端を含んだ長さで宣言されなければなりません。 メンバー変数 `len` は `arr` に格納された文字列のゼロ終端を含まない長さを保持します。 ホスト変数をクエリの入力として使用する際、`strlen(arr)` と `len` が違った場合には短いものが使用されます。

2つ以上の `VARCHAR` ホスト変数は、一行で定義することはできません。 以下のコードは `ecpg` プリプロセッサを混乱させるでしょう:

    VARCHAR v1[128], v2[128];   /* WRONG */

2つの変数は以下のように別々の行で定義されなければなりません:

    VARCHAR v1[128];
    VARCHAR v2[128];

`VARCHAR` は大文字でも小文字でも記述することができますが、混在して記述することはできません。

`char` と `VARCHAR` ホスト変数は、他のSQLのデータ型の値を文字列表現として保持することもできます。

### 33.4.4.2. 特殊なデータ型へのアクセス

ECPGには、PostgreSQLサーバからのいくつかの特殊なデータ型とやりとりするための特殊なデータ型があります。 特に、`numeric`, `decimal`, `date`, `timestamp`, `interval` 型へのサポートを実装しています。 これらのデータ型は複雑な内部構造を持つため、ホスト変数のプリミティブ型（`int`, `long long int`, または `char[]`）に対応させることはできません。 アプリケーションは特別な型としてホスト変数を宣言し、pgtypesライブラリ内の関数を使ってアクセスすることで、これらの型を扱います。 項33.6 で詳細を解説されるpgtypesライブラリは、例えばタイムスタンプにインターバルを加算する際にクエリをSQLサーバに送らずに済ますような、これらの型を扱うための基本的な関数を含んでいます。

移行のサブセクションは、これらの特殊なデータ型を説明します。 pgtypesライブラリ関数についての詳細は 項33.6 を参照してください。

#### 33.4.4.2.1. timestamp, date

以下は、`timestamp` 変数をECPGホストアプリケーションで扱う典型的なパターンです。

最初に、プログラムは `timestamp` 型のためのヘッダファイルをインクルードする必要があります:

    #include <pgtypes_timestamp.h>

次に、宣言セクションで `timestamp` 型のホスト変数を宣言します:

    EXEC SQL BEGIN DECLARE SECTION;
    timestamp ts;
    EXEC SQL END DECLARE SECTION;

そして、ホスト変数へ値を読み込んだら、pgtypesライブラリ関数を使って処理をします。 以降の例では、`timestamp` の値は `PGTYPEStimestamp_to_asc()` 関数によって text (ASCII) 形式に変換されます:

    EXEC SQL SELECT now()::timestamp INTO :ts;

    printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));

This example will show some result like following:

    ts = 2010-06-27 18:03:56.949343

また、DATE型も同じ方法で扱うことができます。 プログラムは `pg_types_date.h` をインクルードし、ホスト変数を date 型として宣言し、`PGTYPESdate_to_asc()` 関数によって DATE の値を text 形式に変換します。 pgtypesライブラリ関数についての詳細は、項33.6 を参照してください。

#### 33.4.4.2.2. interval

`interval` 型の扱い方は `timestamp` や `date` 型と似ています。 但し、`interval` 型の値のために明示的にメモリを確保する必要があります。 言い換えると、この変数のためのメモリ領域はスタックではなくヒープ上に確保されます。

以下にプログラム例を示します:

    #include <stdio.h>
    #include <stdlib.h>
    #include <pgtypes_interval.h>

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        interval *in;
    EXEC SQL END DECLARE SECTION;

        EXEC SQL CONNECT TO testdb;

        in = PGTYPESinterval_new();
        EXEC SQL SELECT '1 min'::interval INTO :in;
        printf("interval = %s\n", PGTYPESinterval_to_asc(in));
        PGTYPESinterval_free(in);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

#### 33.4.4.2.3. numeric, decimal

`numeric` と `decimal` 型の扱い方は `interval` 型と似ています: ポインタ宣言を必要とし、ヒープメモリを確保する必要があり、pgtypesライブラリ関数を使って変数にアクセスします。 pgtypesライブラリ関数の詳細については、項33.6 を参照してください。

`decimal` 型に対する専用の関数は提供されていません。 アプリケーションは処理を行うために pgtypesライブラリ関数を使って `numeric` 変数に変換する必要があります。

以下に `numeric` および `decimal` 型の変数の処理の例を示します。

    #include <stdio.h>
    #include <stdlib.h>
    #include <pgtypes_numeric.h>

    EXEC SQL WHENEVER SQLERROR STOP;

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        numeric *num;
        numeric *num2;
        decimal *dec;
    EXEC SQL END DECLARE SECTION;

        EXEC SQL CONNECT TO testdb;

        num = PGTYPESnumeric_new();
        dec = PGTYPESdecimal_new();

        EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec;

        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0));
        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1));
        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2));

        /* Convert decimal to numeric to show a decimal value. */
        num2 = PGTYPESnumeric_new();
        PGTYPESnumeric_from_decimal(dec, num2);

        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0));
        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1));
        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2));

        PGTYPESnumeric_free(num2);
        PGTYPESdecimal_free(dec);
        PGTYPESnumeric_free(num);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

### 33.4.4.3. 非プリミティブ型のホスト変数

ホスト変数として、配列、typedef、構造体およびポインタも使うことができます。

#### 33.4.4.3.1. 配列

ホスト変数としての配列の使い方には二通りの利用方法があります。 一つ目の使い方は、項33.4.4.1 で説明されたように `char[]` または `VARCHAR[]` の何らかのテキスト文字列を保持するための方法です。 二つ目の使い方は、カーソルを用いずに複数行を返却するクエリ結果を受け取るために使う方法です。 配列を使わない場合、複数行からなるクエリの実行結果を処理するには、カーソルと `FETCH` コマンドを使用する必要があります。 しかし、配列のホスト変数を使うと、複数行を一括して受け取ることができます。 配列の長さはすべての行を受け入れられるように定義されなければなりません。でなければバッファーオーバーフローが発生するでしょう。

以下の例は `pg_database` システムテーブルをスキャンし、利用可能なデータベースのすべてのOIDとデータベース名を表示します:

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        int dbid[8];
        char dbname[8][16];
        int i;
    EXEC SQL END DECLARE SECTION;

        memset(dbname, 0, sizeof(char)* 16 * 8);
        memset(dbid, 0, sizeof(int) * 8);

        EXEC SQL CONNECT TO testdb;

        /* Retrieve multiple rows into arrays at once. */
        EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;

        for (i = 0; i < 8; i++)
            printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

この例は、以下の結果を表示します。（実際の値はローカルな環境に依存します）

    oid=1, dbname=template1
    oid=11510, dbname=template0
    oid=11511, dbname=postgres
    oid=313780, dbname=testdb
    oid=0, dbname=
    oid=0, dbname=
    oid=0, dbname=

#### 33.4.4.3.2. 構造体

メンバー変数の名前がクエリ結果のカラム名に合致する構造体は、複数のカラムを一括して受け取るために利用することができます。 構造体は複数のカラムの値を単一のホスト変数で扱うことを可能にします。

以下の例は、`pg_database` システムテーブルおよび `pg_database_size()` 関数を使って、利用可能なデータベースのOID、名前、サイズを取得します。 この例では、メンバー変数の名前が `SELECT` 結果の各カラムに合致する構造体 `dbinfo_t` が、複数のホスト変数に格納することなく `FETCH` 文の一行の結果を受け取るために使用されています。

    EXEC SQL BEGIN DECLARE SECTION;
        typedef struct
        {
           int oid;
           char datname[65];
           long long int size;
        } dbinfo_t;

        dbinfo_t dbval;
    EXEC SQL END DECLARE SECTION;

        memset(&dbval, 0, sizeof(dbinfo_t));

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
        EXEC SQL OPEN cur1;

        /* when end of result set reached, break out of while loop */
        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            /* Fetch multiple columns into one structure. */
            EXEC SQL FETCH FROM cur1 INTO :dbval;

            /* Print members of the structure. */
            printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size);
        }

        EXEC SQL CLOSE cur1;

この例は、次の結果を示します（実際の値はローカルな環境に依存します）

    oid=1, datname=template1, size=4324580
    oid=11510, datname=template0, size=4243460
    oid=11511, datname=postgres, size=4324580
    oid=313780, datname=testdb, size=8183012

構造体のホスト変数は、多数のカラムを構造体のフィールドとして"吸収"します。 追加のカラムは他のホスト変数に割り当てることができます。 例えば、上記のプログラムは構造体に含まれない `size` 変数を使って以下のように書き換えることができます。

    EXEC SQL BEGIN DECLARE SECTION;
        typedef struct
        {
           int oid;
           char datname[65];
        } dbinfo_t;

        dbinfo_t dbval;
        long long int size;
    EXEC SQL END DECLARE SECTION;

        memset(&dbval, 0, sizeof(dbinfo_t));

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
        EXEC SQL OPEN cur1;

        /* when end of result set reached, break out of while loop */
        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            /* Fetch multiple columns into one structure. */
            EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

            /* Print members of the structure. */
            printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
        }

        EXEC SQL CLOSE cur1;

#### 33.4.4.3.3. Typedefs

新しい型と既存の型を対応付けるためには `typedef` キーワードを使ってください。

    EXEC SQL BEGIN DECLARE SECTION;
        typedef char mychartype[40];
        typedef long serial_t;
    EXEC SQL END DECLARE SECTION;

また、同様に以下を使うこともできます:

    EXEC SQL TYPE serial_t IS long;

この宣言は、宣言セクションの一部である必要はありません。

#### 33.4.4.3.4. ポインタ

ほとんどの一般的な型のポインタを宣言することができます。 但し、自動メモリ確保を使わずにクエリのターゲット変数として使うことはできません。 自動メモリ確保については 項33.7 を参照してください。

    EXEC SQL BEGIN DECLARE SECTION;
        int   *intp;
        char **charp;
    EXEC SQL END DECLARE SECTION;

## 33.4.5. 非プリミティブSQLデータ型の扱い方

本節では、非スカラー型およびユーザ定義のSQLデータ型をECPGアプリケーションで扱う方法を示します。 この内容は、前の説で説明した非プリミティブ型のホスト変数の扱い方とは別のものです。

### 33.4.5.1. 配列

SQLの配列は、ECPGにおいては直接的にはサポートされていません。 SQL配列をC言語の配列のホスト変数に簡単に対応させることはできません。 定められていない振る舞いを引き起こすでしょう。 但し、いくつかの回避策があります。

もし、クエリが配列の 要素 に対して個別にアクセスした場合、ECPGにおける配列の利用を避けることができます。 その際、要素に対応させることができる型のホスト変数を利用しなければなりません。 例えば、カラムの型が `integer` の配列の場合、`int` 型のホスト変数を使用することができます。 同様に、要素の型が `varchar` または `text` の場合、 `char[]` ないし `VARCHAR[]` 型のホスト変数を使用することができます。

以下に例を示します。次のようなテーブルを仮定します:

    CREATE TABLE t3 (
        ii integer[]
    );

    testdb=> SELECT * FROM t3;
         ii
    -------------
     {1,2,3,4,5}
    (1 row)

以下のプログラム例は、配列の4番目の要素を取得し、それを `int` 型のホスト変数に保存します:

    EXEC SQL BEGIN DECLARE SECTION;
    int ii;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :ii ;
        printf("ii=%d\n", ii);
    }

    EXEC SQL CLOSE cur1;

この例は以下のような結果を示します:

    ii=4

複数の配列の要素を、配列型のホスト変数の複数の要素にマッピングするためには、配列型のカラムの各要素とホスト変数配列の各要素は、以下の例のように別々に管理されなければなりません:

    EXEC SQL BEGIN DECLARE SECTION;
    int ii_a[8];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3];
        ...
    }

繰り返しになりますが、以下の例は

    EXEC SQL BEGIN DECLARE SECTION;
    int ii_a[8];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* WRONG */
        EXEC SQL FETCH FROM cur1 INTO :ii_a;
        ...
    }

この場合は正しく動作しません。なぜなら、配列型のカラムをホストの配列変数に直接対応させることはできないからです。

もうひとつの回避策は、配列をホスト変数の `char[]` または `VARCHAR[]` 型に文字列表現として保存することです。 この表現方法についての詳細は 項8.14.2 を参照してください。 このことは、配列にはホストプログラム内で自然な形ではアクセスできないことを意味しています（文字列表現を解析する追加処理が無ければ）。

### 33.4.5.2. 複合型

複合型はECPGでは直接はサポートされていませんが、簡単な回避方法が利用可能です。 利用可能なワークアラウンドは、先に配列において説明されたものと似ています: 各属性に個別にアクセスするか、外部の文字列表現を使います。

以降の例のため、以下の型とテーブルを仮定します:

    CREATE TYPE comp_t AS (intval integer, textval varchar(32));
    CREATE TABLE t4 (compval comp_t);
    INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );

もっとも分かりやすい解決法は、各属性に個別にアクセスすることです。 以下のプログラムは、 `comp_t` 型の各要素を個別に選択することによってサンプルのテーブルからデータを受け取ります:

    EXEC SQL BEGIN DECLARE SECTION;
    int intval;
    varchar textval[33];
    EXEC SQL END DECLARE SECTION;

    /* Put each element of the composite type column in the SELECT list. */
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch each element of the composite type column into host variables. */
        EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

        printf("intval=%d, textval=%s\n", intval, textval.arr);
    }

    EXEC SQL CLOSE cur1;

この例を拡張して、 `FETCH` コマンドの値を格納するホスト変数を一つの構造体にまとめることができます。 構造体の形のホスト変数の詳細については 項33.4.4.3.2 を参照してください。 構造体に変更するために、この例は以下のように変更することができます。 二つのホスト変数 `intval` と `textval` を `comp_t` 構造体のメンバー変数とし、構造体を `FETCH` コマンドで指定します。

    EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
        int intval;
        varchar textval[33];
    } comp_t;

    comp_t compval;
    EXEC SQL END DECLARE SECTION;

    /* Put each element of the composite type column in the SELECT list. */
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Put all values in the SELECT list into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :compval;

        printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
    }

    EXEC SQL CLOSE cur1;

構造体が `FETCH` コマンドで使われていますが、属性名は `SELECT` 句において各々が指定されています。 これは、複合型の値のすべての属性を示す `*` を用いることで拡張することができます。

    ...
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Put all values in the SELECT list into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :compval;

        printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
    }
    ...

この方法であれば、ECPGが複合型そのものを理解できないとしても、複合型はほぼシームレスに構造体に対応させることができます。

最後に、`char[]` または `VARCHAR[]` 型のホスト変数に外部の文字列表現として複合型の値を格納することもできます。 しかし、この方法ではホストプログラムから値のフィールドにアクセスするのは簡単ではありません。

### 33.4.5.3. ユーザ定義の基本型

新しいユーザ定義の基本型は、ECPGでは直接的にはサポートされていません。 外部の文字列表現、`char[]`または`VARCHAR[]` 型のホスト変数を使うことができ、この解決法は多くの型について確かに適切かつ十分です。

以下に 項35.11 に含まれる `complex` 型を使った例を示します。 `complex` from この型の外部文字列表現は `(%lf,%lf)` で、項35.11 の `complex_in()` 関数および `complex_out()` 関数で定義されています。 以下の例は、カラム `a` と `b` に、complex 型の値 `(1,1)` および `(3,3)` を挿入し、その後、それらをテーブルからSELECTします。

    EXEC SQL BEGIN DECLARE SECTION;
        varchar a[64];
        varchar b[64];
    EXEC SQL END DECLARE SECTION;

        EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)');

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex;
        EXEC SQL OPEN cur1;

        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            EXEC SQL FETCH FROM cur1 INTO :a, :b;
            printf("a=%s, b=%s\n", a.arr, b.arr);
        }

        EXEC SQL CLOSE cur1;

この例は、以下の結果を示します。

    a=(1,1), b=(3,3)

その他の回避方法は、ユーザ定義型をECPGにおいて直接的に使うことを避けることであり、ユーザ定義型とECPGが扱えるプリミティブ型を変換する関数またはキャストを作成することです。 ただし、型のキャスト、特に暗黙のものは型システムにおいて慎重に導入されなければなりません。

例を示します。

    CREATE FUNCTION create_complex(r double, i double) RETURNS complex
    LANGUAGE SQL
    IMMUTABLE
    AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;

この定義の後、以下の例は

    EXEC SQL BEGIN DECLARE SECTION;
    double a, b, c, d;
    EXEC SQL END DECLARE SECTION;

    a = 1;
    b = 2;
    c = 3;
    d = 4;

    EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));

以下と同じ効果をもたらします。

    EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');

## 33.4.6. Indicators

上の例ではNULL値を扱いません。 実際、取り出し例では、もしデータベースからNULL値が取り出された場合にはエラーが発生します。 データベースへNULL値を渡す、または、データベースからNULL値を取り出すためには、第二のホスト変数指定をデータを格納するホスト変数それぞれに追加しなければなりません。 第二のホスト変数は指示子と呼ばれ、データがNULLかどうかを表すフラグが含まれます。 NULLの場合、実際のホスト変数の値は無視されます。 以下に、NULL値の取り出しを正しく扱う例を示します。

    EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR val;
    int val_ind;
    EXEC SQL END DECLARE SECTION:

     ...

    EXEC SQL SELECT b INTO :val :val_ind FROM test1;

値がNULLでなければ、指示子変数`val_ind`は0となります。 値がNULLならば負の値となります。

指示子は他の機能を持ちます。 指示子の値が正ならば、値がNULLではありませんが、ホスト変数に格納する際に一部切り詰められたことを示します。

プリプロセッサ `ecpg` に引数 `-r no_indicator` が渡された場合、"no-indicator" モードで動作します。 no-indicator モードでは、指示子変数が指定されなかった場合、（入力および出力において）文字列型に対して空の文字列としてnull値が、整数型に対してはもっとも小さな値が割り当てられます（例えば、`int` の場合 `INT_MIN` です）。

前のページ | ホーム | 次のページ
SQLコマンドの実行 | 上に戻る | 動的SQL