シンプルな同期型のアプリケーションの場合。コマンドの実行は PQexec関数で十分です。しかし、大きな問題をいくつか抱えています。
PQexecは、コマンドが完了するまで待ち状態になります。この間、アプリケーションには他にするべき作業があるかもしれません(たとえばユーザインタフェースの調整など)。このような場合は応答待ちでブロックされたくありません。
制御はPQexec関数の中に埋め込まれてしまっています。したがって、処理中のコマンドをキャンセルするタイミングをフロントエンド側で判断するのは困難です(もっとも、シグナルハンドラからは可能ですが)。
PQexecが返せる PGresult 構造体は1つだけです。もし送信した問い合わせ文字列が複数の SQL コマンドを含んでいる場合、 PQexecは最後のものだけを除いて、残りすべての PGresult を破棄してしまいます。
アプリケーションにとってこのような制限が望ましくない場合は、代わりに PQexecを構成する関数 PQsendQuery と PQgetResult を使用してください。
PQputline や PQputnbytes と同様に、この機能性を使っている古いプログラムは、バックエンドへのデータ送信待ちをブロックしてしまう可能性がありました。この問題に対応するために、PQsetnonblockingが追加されました。
古めのアプリケーションでは、PQsetnonblocking が使われていなかったり、昔の潜在的にブロックする挙動を得ていなかったりしました。新しいプログラムでは、PQsetnonblockingを使うことで、バックエンドと完全にブロックされない接続をすることができるようになりました。
PQsetnonblocking 接続の非ブロック状態を設定します。
int PQsetnonblocking(PGconn *conn, int arg)
arg が 1 の場合は、接続が非ブロック状態になり、 arg が 0 の場合はブロック状態になります。成功した場合は0を、失敗した場合は-1を返します。
非ブロック状態においては、PQputline、 PQputnbytes、PQsendQuery、 PQendcopy の呼び出しはブロックされませんが、その代わり、エラーを返した場合にはそれらの関数をもう一度呼び出さなければいけません。
データベース接続が非ブロックモードになっていて、 PQexecを呼び出したときには、 PQexecが完了するまで、一時的に接続をブロック状態にします。
libpqの多くのものについても、近い将来 PQsetnonblocking が安全に機能するようになると思われます。
PQisnonblocking データベース接続がブロックされているかどうかを返します。
int PQisnonblocking(const PGconn *conn)
接続が非ブロックモードになっている場合は 1 を返し、ブロックモードの場合は 0 を返します。
PQsendQuery 結果を待たずに問い合わせをサーバに送ります。問い合わせはただちに送信され、成功すれば 1 を、失敗すれば 0 を返します(この場合、 PQerrorMessage を使えば失敗した理由の詳細を知ることができます)。
int PQsendQuery(PGconn *conn, const char *query);
PQsendQuery呼び出しが成功したら、 PQgetResultを繰り返し呼び出して、実行結果を取得します。 PQgetResultが NULL を返し、コマンドが完了したことを示すまでは、同じ接続でPQsendQueryを再度呼んではいけません。
PQgetResult 先に実行されたPQsendQueryの結果を逐次待ち、その結果を返します。問い合わせ実行が完了した場合NULLを返します。NULLが返った場合、取り出す結果はそれ以上ありません。
PGresult *PQgetResult(PGconn *conn);
コマンドが完了したことを示す NULL が返るまで、 PQgetResultを繰り返して呼び出さなければなりません(コマンドがなされていない状態で呼び出すと、PQgetResult はただちに NULL を返すだけです)。PQgetResultから得られる NULL でない個々の結果は、前に説明した PGresult のアクセッサ関数と同じものを使って処理してください。処理が終わったらPQclearを使い、個々の結果オブジェクトの領域を解放するのを忘れないように。なお、問い合わせが実行中であり、かつPQconsumeInputを使って必要な応答データを読み込んでいないときだけは、PQgetResultが処理をブロックしてしまうことに注意してください。
PQsendQuery と PQgetResult を使うことで PQexec の問題は 1 つ解決します。つまり、コマンドが複数の SQL コマンドを含んでいる場合でも、これらのコマンドの結果を個々に得ることができるわけです(これは多重処理をシンプルな形で実現します。単一のコマンド文字列に含まれる複数の問い合わせのうち、後にくるものが処理中でもフロントエンドは先に完了した結果から扱うことができるからです)。しかしバックエンドが次の SQL コマンドの処理に入ると、それが完了するまでやはり PQgetResult の呼び出しがフロントエンドをブロックしてしまいます。これを防ぐには、さらに3つの関数をうまく使うことが大切です。
PQconsumeInput バックエンドからの入力が可能になった時点で、それを「吸い取り」ます。
int PQconsumeInput(PGconn *conn);
PQconsumeInput は通常、"エラーなし" を示す 1 を返しますが、何らかの障害があると 0 を返します(この場合は、 PQerrorMessage がセットされます)。この結果は何らかの入力データが、実際に収集されたかどうかを示しているのではないことに注意してください。PQconsumeInput の呼び出し後、アプリケーションは PQisBusy、または必要があれば PQnotifies を呼び出して状態に変化がないか調べることができます。
PQconsumeInput は、結果や通知を扱うようにまだ準備していないアプリケーションからでも呼び出すことができます。このルーチンは有効なデータを読み込んでバッファに保存し、結果として select による読み込み準備完了の通知をリセットします。したがってアプリケーションは PQconsumeInput を使うと select() の検査条件をただちに満たすことができますから、あとはゆっくりと結果を調べてやればいいわけです。
PQisBusy この関数が 1 を返したのであれば、問い合わせは処理の最中で、 PQgetResult も入力を待ったままブロック状態になってしまうでしょう。0 が返ったのであれば、PQgetResultを呼び出してもブロックされないことが保証されます。
int PQisBusy(PGconn *conn);
PQisBusy自身はバックエンドからデータを読み込む操作をしません。ですから、まずは最初に PQconsumeInput を呼び出さなければいけません。さもなければ、ビジー状態がいつまでも続くことになるでしょう。
PQflush 送信待ちのデータをバックエンドへ送信します。データの送信に成功するか送信待ちのデータがなければ、0を返します。何らかの理由で失敗した場合は、EOF を返します。
int PQflush(PGconn *conn);
非ブロック接続時には、select() を呼び出して応答を判定する前に、PQflush を呼び出しておかなければいけません。0 が返ってきた場合、バックエンドへの送信待ちデータがないことが保証されます。 PQsetnonblocking を使用しているアプリケーションのみでこの関数が必要になります。
PQsocket バックエンドとの接続ソケットに対するファイル記述子番号を得ます。有効な記述子なら値は0以上です。-1の場合は、バックエンドとの接続がまだオープンされていないことを示します。
int PQsocket(const PGconn *conn);
selectを実行する際に使うバックエンドへのソケット記述子を得るには、このPQsocketを使います。アプリケーションはこの関数によってバックエンドからの応答、あるいはその他の状態を待つことができるようになります。もし select() がバックエンドへのソケットからデータを読み込める状態になったことを示したら、 PQconsumeInput を呼び出してデータを読み込みます。それから PQisBusy や PQgetResult、あるいは必要ならPQnotifiesを使って応答を処理します。
PQflush が 0 を返して、バックエンドへの送信を待っているバッファリングされたデータがないことを示すまで、 PQsetnonblocking を使った非ブロック接続は、 select() を使用すべきではありません。
これらの関数を使用するフロントエンドは、select を使ったメインループを備え、応答しなくてはいけないすべての状態を待ちます。やがてこの状態のうち1つ(select 関数の点から見れば、 PQsocket で確認したファイル記述子から読み込めるデータ)がバックエンドから入力可能になります。メインループは入力が可能になったことを検出したら、PQconsumeInputを呼び出して入力を読み込みます。続いてPQisBusyを呼び出し、 PQisBusyがFALSE(0)を返したら、さらに PQgetResultを呼び出します。あるいは PQnotifies を呼び出して、通知メッセージを検出する場合もあります(Section 1.6を参照)。
PQsendQuery/PQgetResult を使うと、バックエンドで処理中のコマンドをフロントエンドからキャンセルするよう要求することができます。
PQrequestCancel PostgreSQLに現在のコマンドの処理を中断するよう要求します。
int PQrequestCancel(PGconn *conn);
キャンセル要求の受け入れが成功すれば、1 を、そうでなければ 0 を返します(失敗した場合は、PQerrorMessage で原因を知ることができます)。しかし要求の受け入れが成功したとしても、その要求の効果が出ることはまったく保証していません。したがって、アプリケーションは PQrequestCancel の戻り値にかかわらず、 PQgetResult を使った通常の結果読み込み処理を継続しなければいけません。もしキャンセル操作が有効であれば、現在のコマンドは間もなく中断され、エラーが結果として返ります。キャンセル操作に失敗した場合(つまりバックエンドがすでにコマンド処理を終了していたため)、目に見える結果は何も出てこなくなります。
なお、処理中のコマンドがトランザクションの一部だと、キャンセル処理によってトランザクション全体がアボートされます。
PQrequestCancel はシグナルハンドラから起動しても大丈夫です。また、単に PQexec と組み合わせて使うことができます。たとえば psql は PQexecを通して発行された問い合わせのキャンセル処理を、 SIGINT シグナルハンドラから PQrequestCancel を起動することで、対話式に実行できるようになっています。なお、接続がオープンされていない、あるいはバックエンドがコマンドの処理をしていない状態では、PQrequestCancel の呼び出しは何の効果もありません。