27.4. 非同期コマンドの処理

PQexec関数は普通の同期処理のアプリケーションにおけるコマンドの送信に適したものです。 しかし、一部のユーザにとって重要な問題となり得る、2つの問題があります。

アプリケーションにとってこのような制限が望ましくない場合は、代わりに PQexec を構成する関数 PQsendQueryPQgetResult を使用してください。 また、PQsendQueryParamsPQsendQueryPreparedもあり、PQgetResult を使用して、それぞれPQexecParamsPQexecPrepared と同等の機能を行なうことができます。

PQsendQuery

結果を待つことなく、サーバにコマンドを発行します。 コマンドの登録に成功した場合1が、失敗した場合0が返されます。 (後者の場合、PQerrorMessage を使用して失敗についてのより多くの情報を取り出してください。)

int PQsendQuery(PGconn *conn, const char *command);

PQsendQuery呼び出しが成功したら、PQgetResultを繰り返し呼び出して、実行結果を取得します。 PQgetResult がヌルポインタを返し、コマンドが完了したことを示すまでは、(同じ接続で)PQsendQuery を再度呼んではいけません。

PQsendQueryParams

結果を待つことなく、サーバにコマンドとパラメータとを分けて発行します。

int PQsendQueryParams(PGconn *conn,
                      const char *command,
                      int nParams,
                      const Oid *paramTypes,
                      const char * const *paramValues,
                      const int *paramLengths,
                      const int *paramFormats,
                      int resultFormat);

これは、問い合わせのパラメータが問い合わせ文字列と分けて指定できる点を除き、PQsendQueryと同じです。 この関数のパラメータはPQexecParamsと同様に扱われます。 PQexecParams同様、これは2.0プロトコルでは動作しませんし、問い合わせ文字列には1つのコマンドしか指定できません。

PQsendQueryPrepared

結果を待つことなく、指定したパラメータで準備済文の実行要求を送信します。

int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

これはPQsendQueryParamsと似ていますが、実行されるコマンドは、問い合わせ文字列ではなく、事前に準備された文の名前で指定されます。 この関数のパラメータはPQexecPreparedと同様に扱われます。 PQexecPrepared同様、これは2.0プロトコルでは動作しません。

PQgetResult

以前に呼び出したPQsendQueryPQsendQueryParamsPQsendQueryPrepared から次の結果を待ち、その結果を返します。 コマンドが完了し、これ以上結果がない場合は、ヌルポインタが返されます。

PGresult *PQgetResult(PGconn *conn);

PQgetResultは、コマンドの完了を示すヌルポインタが返るまで、繰り返し呼び出さなければなりません。 (コマンド実行中以外での呼び出しでは、PQgetResultは単にヌルポインタを返します。) PQgetResultの非ヌルの結果はそれぞれ前述と同じPGresultアクセッサ関数を使用して処理されなければなりません。 各結果オブジェクトに対する処理が終ったら、そのオブジェクトをPQclearを使用して解放することを忘れないでください。 コマンドが活動中、かつ、必要な応答データがまだPQconsumeInputで読み込まれていない場合にのみ、PQgetResultがブロックすることに注意してください。

PQsendQueryPQgetResult を使うことで PQexec の問題は 1 つ解決します。 つまり、コマンドが複数の SQL コマンドを含んでいる場合でも、これらのコマンドの結果を個々に得ることができるわけです (これは多重処理をシンプルな形で実現します。 単一のコマンド文字列に含まれる複数の問い合わせのうち、後ろのものが処理中でもフロントエンドは先に完了した結果から扱うことができるからです)。 しかしサーバが次の SQL コマンドの処理に入ると、それが完了するまでやはり PQgetResult の呼び出しがフロントエンドをブロックしてしまいます。 さらに以下の2つの関数をうまく使用してこれを防ぐことができます。

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 を呼び出す必要があります。 そうしないとビジー状態がいつまでも続きます。

これら3関数を使用するアプリケーションは通常、 select() もしくは poll() を使用するメインループを持ち、対応しなければならないすべての状態を待機しています。 その内の1つの条件は、サーバからの利用可能な入力となるでしょう。 これは、select()の見地からは、PQsocketで識別されるファイル記述子上で読み込み可能なデータがあることを意味します。 メインループが入力準備完了を検出すると、その入力を読み込みためにPQconsumeInputを呼び出さなければなりません。 そして、PQisBusyを、更にPQisBusyが偽(0)を返す場合にPQgetResultも呼び出すことができます。 また、PQnotifiesを呼び出して、NOTIFYメッセージ( 項27.6を参照)を検出することもできます。

また、PQsendQuery/PQgetResultを使用するクライアントは、サーバで処理中のコマンドに対してキャンセルを試行することができます。

PQrequestCancel

サーバに現在のコマンドの廃棄処理を要求します。

int PQrequestCancel(PGconn *conn);

キャンセル要求の受け入れが成功すれば 1 を、そうでなければ 0 を返します。 (失敗した場合は、PQerrorMessage で原因を知ることができます。) しかし要求の受け入れが成功したとしても、その要求の効果が出ることはまったく保証していません。 したがって、アプリケーションは PQrequestCancel の戻り値にかかわらず、PQgetResult を使った通常の結果読み込み処理を継続しなければいけません。 もしキャンセル操作が有効であれば、現在のコマンドは間もなく中断され、エラーが結果として返ります。 キャンセル操作に失敗した場合(つまりバックエンドがすでにコマンド処理を終了していたため)、目に見える結果は何も出てこなくなります。

なお、処理中のコマンドがトランザクションブロックの一部である場合はキャンセル処理によってトランザクション全体がアボートされます。

PQrequestCancel はシグナルハンドラから起動しても大丈夫です。 また、キャンセルの決定がシグナルハンドラでなされる場合は、普通の PQexec と組み合わせて使うことができます。 例えば psqlPQexecを通して発行された問い合わせのキャンセル処理を、SIGINT シグナルハンドラから PQrequestCancel を起動することで、対話式に実行できるようになっています。

上述の関数を使用して、データベースサーバからの入力待ちのためのブロックを行なわずに済みます。 しかし、まだ、サーバへの出力送信を待つためにアプリケーションはブロックする可能性があります。 これは、比較的あまり発生しませんが、非常に長いSQLコマンドやデータ値が送信される場合に発生することがあります。 (しかしアプリケーションがCOPY IN経由でデータを送信する場合よく発生します。) この発生を防ぎ、完全な非ブロックのデータベース操作を行なうためには、更に以下の関数を使用してください。

PQsetnonblocking

接続の非ブロック状態を設定します。

int PQsetnonblocking(PGconn *conn, int arg);

argが1の場合、接続状態を非ブロックに設定します。 argが0の場合はブロックに設定します。 問題がなければ0が、エラー時は-1が返ります。

非ブロック状態では、PQsendQueryPQputlinePQputnbytesPQendcopyの呼び出しはブロックされず、これらを再度呼び出す必要がある場合はエラーが返ります。

PQexecは非ブロックモードには従いません。この関数の呼び出しは、必ずブロック方式で動作します。

PQisnonblocking

データベース接続のブロック状態を返します。

int PQisnonblocking(const PGconn *conn);

接続が非ブロック状態の場合は1が、ブロック状態の場合は0が返ります。

PQflush

キューに蓄えられたサーバへの出力データの吐き出しを行ないます。 成功時(および送信キューが空の場合)は0が返ります。 何らかの原因で失敗した場合は-1が、送信キュー内のデータをすべて送信できなかった場合は1が返ります。 (これは接続が非ブロックの場合にのみ発生します。)

int PQflush(PGconn *conn);

非ブロック接続時にはコマンドやデータを送信した後に、PQflushを呼び出してください。 1が返った場合、ソケットの書き込み準備ができるまで待ち、再度呼び出してください。 これを0が返るまで繰り返してください。 PQflushが0を返した後は、ソケットの読み込み準備が整うまで待ち、上述のように応答を読みとってください。