ファイヤープロジェクト
問い合わせ
2004-02-24T17:50+09:00   matsu
libpqを使用して問い合わせを行なうプログラムを作成してみた.
libpqで問い合わせを行なうプログラムの場合,まず文字列としてSQL文を作成しサーバに送信する.ここまではpsqlなどで問い合わせを行なうのとさほど変わらない.ただ,サーバからの応答を確認する処理が多少面倒である.libpqで問い合わせを行なうプログラムの全体的な流れを以下に示す.
  1. サーバに接続
  2. 接続確認
  3. SQL文発行
  4. サーバの応答確認
  5. サーバからの応答にデータがある場合(※),それを取り出す.
  6. 切断
本頁では,libpqを使用してサーバにテーブル作成,INSERT,SELECT,テーブルDROPを行なうサンプルを作成する.サーバからの応答にデータがある場合の処理は割りとボリュームがありそうなので,今回のサンプルではタプル数だけを取り出すこととし,細かい処理は別頁に記述することにする.
※ 主にSELECT文を発行した場合.
libpqを使用してPostgreSQLに問い合わせを行なうサンプルを以下に示す.
このサンプルの行なう処理概要を以下に示す.
  1. テーブルhogeを作成
  2. 第一引数で指定した数だけテーブルhogeにINSERTする.
  3. 第二引数で指定した数だけテーブルhogeからSELECTする.
  4. テーブル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
致命的なエラー.
PGRES_TUPLES_OKが返った場合は,さらにタプルの取り出しなどの操作ができる(詳細は次次節).サンプルのcreateOrDropTable関数の場合は,正常ならばPGRES_COMMAND_OKが返るはずとしてswitch文を処理し,終了している.以下の関数を使用すると,PGresultStatusで取得したステータスコードの説明文字列を取得することができる.
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が返るのでタプル数のチェックは重要である.
matsu(C)
Since 2002
Mail to matsu