33.3. SQLコマンドの実行では、埋め込みSQLプログラムでどのようにSQL文を実行するのかについて説明しました。 このSQL文の中には固定値しか使用しないものや、ユーザが指定する値をSQL文の中に挿入する手段を提供しないもの、問い合わせが返す値をプログラムで処理する手段を提供しないものがありました。 この種のSQL文は実際のアプリケーションでは役に立ちません。 本節では、ホスト変数という単純な機構を使用した、Cプログラムと埋め込みSQL文との間でデータをやり取りする方法を詳細に説明します。 埋め込みSQLプログラムでは、SQL文をホスト言語となるCプログラムコードにおけるゲストとみなします。 したがって、Cプログラムの変数はホスト変数と呼ばれます。
PostgreSQLバックエンドとECPGアプリケーションの間で値をやり取りするその他の方法は、 33.7. 記述子領域の使用 で説明されているSQLデスクリプタを使う方法です。
埋め込みSQLにおけるCプログラムとSQL文との間でのデータのやり取りは特に単純です。 値に適切な引用符を付与するといった、様々な複雑な処理を伴う、プログラムにデータを文中に貼り付けさせるという方法はなく、単にSQL文の中に、先頭にコロンを付けたC変数名を書くだけです。 以下に例を示します。
EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);
このSQL文は、v1
とv2
という2つのC変数を参照し、また、通常のSQL文字列リテラルも使用しています。
これは、使用できるデータの種類は1つだけという制限がないことを表しています。
SQL文内にCの変数を挿入するこの様式は、SQL文で値式が想定されている所であればどこでも動作します。
例えば問い合わせ内のパラメータとして、プログラムからデータベースへデータを渡す、もしくは、データベースからプログラムへデータを渡すためには、このようなデータを含むように意図された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コマンドで使用する予定がない変数はこの特別なセクションの外側で宣言されます。
構造体や共用体の定義もまた、DECLARE
セクションの内側で表す必要があります。
さもないと、プリプロセッサはその定義が不明であるために、これらの型を扱うことができません。
ここまでで、プログラムで生成したデータをSQLコマンドに渡すことができるようになりました。
しかし、どのように問い合わせの結果を取り出すのでしょうか?
この目的のために、埋め込みSQLでは、通常のSELECT
とFETCH
を派生した、特殊なコマンドを提供しています。
これらのコマンドは特別なINTO
句を持ち、ここで返された値をどのホスト変数に格納すればよいかを指定します。
SELECT
は単一行を返却する問い合わせに使用され、FETCH
は複数の行を返却する問い合わせにおいてカーソルととともに使用されます。
以下にサンプルを示します。
/* * 以下のテーブルを前提とする * 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
句が通常のすべての句の後ろに現れています。
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言語のデータ型に対応するかが示されています。 与えられたPostgreSQLのデータ型へ値を書き込みまたは読み込みしたい場合には、対応するC言語のデータ型の変数を宣言セクションにおいて宣言しなければなりません。
表33.1 PostgreSQLデータ型とC言語変数型の対応
PostgreSQLデータ型 | ホスト変数型 |
---|---|
smallint | short |
integer | int |
bigint | long long int |
decimal | decimal [a] |
numeric | numeric [a] |
real | float |
double precision | double |
smallserial | short |
serial | int |
bigserial | long long int |
oid | unsigned int |
character( , varchar( , text | char[ , VARCHAR[ [b] |
name | char[NAMEDATALEN] |
timestamp | timestamp [a] |
interval | interval [a] |
date | date [a] |
boolean | bool [c] |
[a] この型は特別なライブラリ関数を通してのみアクセスできます; 33.4.4.2. 特殊なデータ型へのアクセス を参照。 [b] [c] ネイティブでなければ |
varchar
や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
が違った場合には短いものが使用されます。
VARCHAR
は大文字でも小文字でも記述することができますが、混在して記述することはできません。
char
と VARCHAR
ホスト変数は、他のSQLのデータ型の値を文字列表現として保持することもできます。
ECPGには、PostgreSQLサーバからのいくつかの特殊なデータ型とやりとりするための特殊なデータ型があります。
特に、numeric
, decimal
, date
, timestamp
, interval
型へのサポートを実装しています。
これらのデータ型は複雑な内部構造を持つため、ホスト変数のプリミティブ型(int
, long long int
, または char[]
)に対応させることはできません。
アプリケーションは特別な型としてホスト変数を宣言し、pgtypesライブラリ内の関数を使ってアクセスすることで、これらの型を扱います。
33.6. pgtypes ライブラリ で詳細を解説されるpgtypesライブラリは、例えばタイムスタンプにインターバルを加算する際にクエリをSQLサーバに送らずに済ますような、これらの型を扱うための基本的な関数を含んでいます。
以降のサブセクションは、これらの特殊なデータ型を説明します。 pgtypesライブラリ関数についての詳細は33.6. pgtypes ライブラリを参照してください。
以下は、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型も同じ方法で扱うことができます。
プログラムは pgtypes_date.h
をインクルードし、ホスト変数を date 型として宣言し、PGTYPESdate_to_asc()
関数によって DATE の値を text 形式に変換します。
pgtypesライブラリ関数についての詳細は、33.6. pgtypes ライブラリ を参照してください。
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; }
numeric
と decimal
型の扱い方は interval
型と似ています: ポインタ宣言を必要とし、ヒープメモリを確保する必要があり、pgtypesライブラリ関数を使って変数にアクセスします。
pgtypesライブラリ関数の詳細については、33.6. pgtypes ライブラリ を参照してください。
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)); /* decimalの値を表示するためdecimalをnumericに変換する。 */ 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; }
ホスト変数として、配列、typedef、構造体およびポインタも使うことができます。
ホスト変数としての配列の使い方には二通りの利用方法があります。
一つ目の使い方は、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; /* 複数行を一度に配列へと取り出す。 */ 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=
メンバー変数の名前がクエリ結果のカラム名に合致する構造体は、複数のカラムを一括して受け取るために利用することができます。 構造体は複数のカラムの値を単一のホスト変数で扱うことを可能にします。
以下の例は、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; /* 結果集合の最後に到達したら、whileループから抜ける */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* 複数列を1つの構造体に取り込む。 */ EXEC SQL FETCH FROM cur1 INTO :dbval; /* 構造体のメンバを表示する。 */ 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; /* 結果集合の最後に到達したら、whileループから抜ける */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* 複数列を1つの構造体に取り込む。 */ EXEC SQL FETCH FROM cur1 INTO :dbval, :size; /* 構造体のメンバを表示する。 */ printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size); } EXEC SQL CLOSE cur1;
新しい型と既存の型を対応付けるためには 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.7. 記述子領域の使用 を参照してください。
EXEC SQL BEGIN DECLARE SECTION; int *intp; char **charp; EXEC SQL END DECLARE SECTION;
本節では、非スカラー型およびユーザ定義のSQLデータ型をECPGアプリケーションで扱う方法を示します。 この内容は、前の説で説明した非プリミティブ型のホスト変数の扱い方とは別のものです。
SQLの多次元配列は、ECPGにおいては直接的にはサポートされていません。 SQLの1次元配列をC言語の配列のホスト変数に対応させることはできますし、その逆もできます。 しかし、文の作成時にはecpgがその列の型を知らないので、C言語の配列を対応するSQLの配列に入力できるか確かめられません。 SQL文の出力を処理する時には、ecpgは必要な情報を持っていますので、どちらも配列であるか確かめます。
もし、クエリが配列の 要素 に対して個別にアクセスした場合、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) { /* 間違い */ EXEC SQL FETCH FROM cur1 INTO :ii_a; ... }
この場合は正しく動作しません。なぜなら、配列型のカラムをホストの配列変数に直接対応させることはできないからです。
もうひとつの回避策は、配列をホスト変数の char[]
または VARCHAR[]
型に文字列表現として保存することです。
この表現方法についての詳細は 8.15.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; /* SELECTリストに複合型の列の各要素を書く。 */ 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) { /* 複合型の列の各要素をホスト変数に取り出す。 */ 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; /* SELECTリストに複合型の列の各要素を書く。 */ 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) { /* SELECTリストの値をすべて1つの構造体に取り込む。 */ 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) { /* SELECTリストの値をすべて1つの構造体に取り込む。 */ EXEC SQL FETCH FROM cur1 INTO :compval; printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr); } ...
この方法であれば、ECPGが複合型そのものを理解できないとしても、複合型はほぼシームレスに構造体に対応させることができます。
最後に、char[]
または VARCHAR[]
型のホスト変数に外部の文字列表現として複合型の値を格納することもできます。
しかし、この方法ではホストプログラムから値のフィールドにアクセスするのは簡単ではありません。
新しいユーザ定義の基本型は、ECPGでは直接的にはサポートされていません。
外部の文字列表現、char[]
またはVARCHAR[]
型のホスト変数を使うことができ、この解決法は多くの型について確かに適切かつ十分です。
以下に35.11. ユーザ定義の型に含まれるcomplex
型を使った例を示します。
この型の外部文字列表現は(%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)');
上の例では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
です)。