問い合わせ
libpqを使用して問い合わせを行なうプログラムを作成してみた.
libpqで問い合わせを行なうプログラムの場合,まず文字列としてSQL文を作成しサーバに送信する.ここまではpsqlなどで問い合わせを行なうのとさほど変わらない.ただ,サーバからの応答を確認する処理が多少面倒である.libpqで問い合わせを行なうプログラムの全体的な流れを以下に示す.
- サーバに接続
- 接続確認
- SQL文発行
- サーバの応答確認
- サーバからの応答にデータがある場合(※),それを取り出す.
- 切断
※ 主にSELECT文を発行した場合.
libpqを使用してPostgreSQLに問い合わせを行なうサンプルを以下に示す.
このサンプルの行なう処理概要を以下に示す.
- テーブルhogeを作成
- 第一引数で指定した数だけテーブルhogeにINSERTする.
- 第二引数で指定した数だけテーブルhogeからSELECTする.
- テーブルhogeをDROP
gcc -Wall -I/usr/include/postgresql/ -lpq query.cそして実行.
$> ./a.out 1024 10 "host=dbserver user=matsu password=hogefuga dbname=firstdb" insert_count : 1024 select_count : 10 conninfo : host=dbserver user=matsu password=hogefuga dbname=firstdb ### start connect 0 0 ### PQconnectdb OK ### end connect 0 0 ### ### start createTable 0 0 ### NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'hoge_pkey' for table 'hoge' ### end createTable 0 0 ### ### insert start 0 0 ### insert completed (1024/1024) ### end insert 7 7 ### ### start select 7 0 ### hit 764 hit 298 hit 542 hit 195 hit 364 hit 931 hit 219 hit 601 hit 847 hit 661 select completed (10/10) ### end select 7 0 ### ### start dropTable 7 0 ### ### end dropTable 7 0 ###いくつかのポイントで,全体の開始時間からの経過時間と,該当処理からの経過時間を出力している.処理の大きさを大きくし,処理中に別途psqlなどで調べると,確かにテーブルhogeが作成され,INSERTされていることが確認できる(※).
※ libpqを使用した場合でもAutoCommitのようだ.
サンプルではCREATEとDROPはそれぞれ関数createTableとdropTableで行なっている.それぞれの関数ではまずSQL文を作成する.
char *sql = "create table hoge (id int primary key, value int)";
char *sql = "drop table hoge";そして両者の共通関数createOrDropTableでサーバへ要求し応答のチェックを行なっている.サンプルではサーバへの全ての問い合わせは以下の関数で行なう(※).
PGresult *PQexec(PGconn *conn, const char *query);第一引数はサーバへの接続オブジェクト,第二引数は問い合わせ文である.第二引数は従来通り普通のSQL文である(ただし;は不要).当然ながら接続オブジェクトで示される該当接続は正常でなければならない(サンプルではconnectToServerで確認した).返り値のPGresultはサーバからの応答データが格納されている.これがNULLで返るのは,libpq内部で致命的なエラーが発生し,PGresultのための領域が確保できなかった場合である.この場合は,接続オブジェクトを使用してPQerrorMessageでエラー情報を取得する.
result = PQexec (connection, sql);
if (result == NULL)
{
fprintf (stderr, "%s\n", PQerrorMessage (connection));
returnValue = 1;
}
PGresultにはさまざまなデータが格納されているが,直接アクセスしてはいけない.PGresultからのデータの取り出しにはアクセッサを使用する.まず,おそらくいかなる問い合わせも以下の関数で,問い合わせのステータスを取得する.
ExecStatusType PQresultStatus(const PGresult *res);返り値について以下に示す.
- PGRES_EMPTY_QUERY
- クエリが空文だった.
- PGRES_COMMAND_OK
- サーバから返るデータのない問い合わせが成功.
- PGRES_TUPLES_OK
- サーバからデータ(タプル)が返った.
- PGRES_COPY_OUT
- サーバからのコピーアウトを開始した...よくわからない.
- PGRES_COPY_IN
- サーバからのコピーインを開始した...よくわからない.
- PGRES_BAD_RESPONSE
- 予期しない応答
- PGRES_NONFATAL_ERROR
- エラー.
- PGRES_FATAL_ERROR
- 致命的なエラー.
const char *PQresStatus(ExecStatusType status);さらに以下の関数で,サーバからのエラーメッセージを取り出すことができる.
const char *PQresultErrorMessage(PGresult *res);サンプルのcreateOrDropTable関数ではPGRES_BAD_RESPONSE,PGRES_NONFATAL_ERROR,PGRES_FATAL_ERRORのいずれかが返った場合にこの関数を使用した.ところでPGresultはPQexecを実行する度に作成されるので,不要になったら以下の関数で開放しないとメモリリークする.
void PQclear(PQresult *res);
※ これは同期問い合わせである.別頁で非同期問い合わせについて記述する.
サンプルではINSERTはinsertRecords関数で行なっている.基本的にCREATE,DROPの場合と同じ処理であるが,コマンドラインで指定した数だけINSERTするようにした.ここで試しにPQclearをコメントアウトして実行してみたところ,メモリ使用量がどんどん増えつづけ,102400レコードのINSERTで8M程浪費した.PQclearは忘れずに正しいポイントで行なうように注意したい.
サンプルではSELECTはselectRecords関数で行なっている.SELECTでは正常にいくとPQresultStatusの値はPGRES_TUPLES_OKが返るので,switch文はCREATE,DROP,INSERTの場合と異なる構成になっている.で,PGRES_TUPLES_OKが返った場合はPGresultのアクセッサでいろいろな情報をとりだして処理を進めるわけだが,詳細は別頁に記述するとして,今回は選択された行数のみを取り出して出力した.
tupleNum = PQntuples(result); fprintf (stdout, "hit %d\n", tupleNum);PQntuplesが選択された行数を返す関数である.
int PQntuples(PGresult *res);選択行数が0行でもPQresultStatusからはPGRES_TUPLES_OKが返るのでタプル数のチェックは重要である.

