PostgreSQLのECPGで、SQL実行エラー時のみデバッグログを出力する例

PostgreSQLに対して、ECPGで以下のSQL実行を行い、**SQL実行エラー時のみそのSQL実行のデバッグログを出力**する簡単なプログラム例を示します。
* ローカルで稼働中のPostgreSQLに対して、DBユーザtestuserでデータベースtestdbに接続する。
* (もし未作成であれば) テーブルtesttblを作成する。テーブルtesttblは、主キーであるinteger型の1カラムnumだけを持つ。
* プログラムの引数で指定された整数値をテーブルtesttblにINSERTする。
* SQL実行がエラーになった場合は、そのSQL実行のデバッグログを標準エラー出力に出力する。

これらを実現するECPGプログラムの簡単な例は下記です。

```c:debug.pgc
#include <stdio.h>
#include <stdlib.h>

/*
 * SQL実行でエラーが発生したら、デバッグログを出力のための関数として
 * output_debug()を呼び出すようにグローバルに宣言する。
 */
static void output_debug(void);
EXEC SQL WHENEVER SQLERROR CALL output_debug();

/*
 * デバッグログを一時保存するメモリストリーム関連の変数を宣言する。
 */
static char *debug_buf;
static size_t debug_bufsize;
static FILE *debug_fp;

/*
 * デバッグログを一時保存するメモリストリームをクローズする関数を宣言する。
 */
static void free_debug_buf(void);

/*
 * ECPGの各内部関数を実行する前に、それまでにメモリストリームに
 * 一時保存されていたデバッグログをクリアする。これにより、
 * メモリストリームには、これから実行する各内部関数で生成された
 * デバッグログのみが一時保存される。
 *
 * 今回のコード例ではECPGconnect()、ECPGdo()、ECPGprepare()、
 * ECPGtrans()、ECPGdisconnect()のみに 対応しており、必要に応じて
 * 他の内部関数についても対応しなければならない可能性がある。
 */
#define ECPGconnect(a, b, c, d, e, f, g)	\
	do {	\
		rewind(debug_fp);	\
		ECPGconnect(a, b, c, d, e, f, g);		\
	} while (0)

#define ECPGdo(a, b, c, d, e, f, g, ...)	\
	do {	\
		rewind(debug_fp);	\
		ECPGdo(a, b, c, d, e, f, g, __VA_ARGS__);	\
	} while (0)

#define ECPGprepare(a, b, c, d, e)	\
	do {	\
		rewind(debug_fp);	\
		ECPGprepare(a, b, c, d, e);	\
	} while (0)

#define ECPGtrans(a, b, c)	\
	do {	\
		rewind(debug_fp);	\
		ECPGtrans(a, b, c);	\
	} while (0)

#define ECPGdisconnect(a, b)	\
	do {	\
		rewind(debug_fp);	\
		ECPGdisconnect(a, b);	\
	} while (0)

/*
 * このプログラムのメイン関数。
 * テーブルtesttblにINSERTするレコードの整数値を第1引数で指定できる。
 */
int
main(int argc, char *argv[])
{
	/*
	 * ECPGで使うローカル変数を宣言する。
	 */
	EXEC SQL BEGIN DECLARE SECTION;
	char *sql = "INSERT INTO testtbl VALUES (?)";
	int num = 0;
	EXEC SQL END DECLARE SECTION;

	/*
	 * 第1引数が指定された場合は、引数の整数値をINSERTする。
	 */
	if (argc > 1)
		num = atoi(argv[1]);

	/*
	 * デバッグログを一時保存するメモリストリームをオープンする。
	 */
	debug_fp = open_memstream(&debug_buf, &debug_bufsize);
	if (!debug_fp)
	{
		fprintf(stderr, "could not open dynamic memory buffer stream\n");
		exit(1);
	}

	/*
	 * デバッグログの生成を有効化する。
	 */
	ECPGdebug(1, debug_fp);

	/*
	 * ローカル内で稼働中のPostgreSQLに対して、DBユーザtestuserで
	 * データベースtestdbに接続する。
	 */
	EXEC SQL CONNECT TO testdb@localhost:5432 USER testuser;

	/*
	 * もし未作成であれば、テーブルtesttblを作成する。
	 */
	EXEC SQL CREATE TABLE IF NOT EXISTS testtbl (num integer primary key);

	/*
	 * 動的SQLを使って、テーブルtesttblにレコードを1件INSERTする。
	 */
	EXEC SQL PREPARE teststmt FROM :sql;
	EXEC SQL EXECUTE teststmt USING :num;

	/*
	 * トランザクションを完了する。
	 */
	EXEC SQL COMMIT;

	/*
	 * PostgreSQLへの接続をクローズする。
	 */
	EXEC SQL DISCONNECT;

	/*
	 * デバッグログを一時保存するメモリストリームをクローズする。
	 */
	free_debug_buf();

	return 0;
}

/*
 * SQL実行でエラーが発生したときに呼び出される、
 * デバッグログを出力するための関数。
 */
static void
output_debug(void)
{
	/*
	 * メモリストリームに一時保存されているデバッグログを標準エラー出力に
	 * 出力する。
	 */
	fflush(debug_fp);
	fprintf(stderr, "%s\n", debug_buf);

	/*
	 * デバッグログを一時保存するメモリストリームをクローズする。
	 */
	free_debug_buf();
	exit(1);
}

/*
 * デバッグログを一時保存するメモリストリームをクローズする関数。
 */
static void
free_debug_buf(void)
{
	fclose(debug_fp);
	free(debug_buf);
}
```

このECPGプログラムは以下のとおりコンパイルします。以下では、PostgreSQLのインストール先を/opt/pgsqlディレクトリとします。

```bash
$ ecpg debug.pgc
$ gcc -o debug debug.c -I/opt/pgsql/include -L/opt/pgsql/lib -lecpg
```

プログラムの実行例は以下のとおりです。

```bash
$ ./debug 1
$ ./debug 2
$ ./debug 2
[67125]: ecpg_execute on line 114: query: INSERT INTO testtbl VALUES ($1); with 1 parameter(s) on connection testdb
[67125]: ecpg_execute on line 114: using PQexecPrepared for "INSERT INTO testtbl VALUES ($1)"
[67125]: ecpg_free_params on line 114: parameter 1 = 2
[67125]: ecpg_check_PQresult on line 114: bad response - ERROR:  duplicate key value violates unique constraint "testtbl_pkey"
DETAIL:  Key (num)=(2) already exists.
CONTEXT:  unnamed portal with parameters: $1 = '2'
[67125]: raising sqlstate 23505 (sqlcode -403): duplicate key value violates unique constraint "testtbl_pkey" on line 114
```