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

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

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

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つのコマンドしか指定できません。

PQsendPrepare

指定パラメータを持つ準備された文の作成要求を送信します。 その完了を待ちません。

int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

これはPQprepareの非同期版です。 要求の登録に成功した場合1が、失敗した場合0が返されます。 呼び出しの成功の後、サーバが準備された文の生成に成功したかを確認するためにはPQgetResultを呼び出してください。 この関数のパラメータはPQprepareと同様に扱われます。 PQprepare同様、これは2.0プロトコルの接続では動作しません。

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プロトコルでは動作しません。

PQsendDescribePrepared

指定した準備された文に関する情報入手要求を送ります。入手完了まで待機しません。

int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

これはPQdescribePreparedの非同期版です。 要求の受付けが可能であれば1が返されます。不可能であれば0が返されます。 呼び出しに成功した後、PQgetResultを呼び出して結果を入手してください。 この関数のパラメータは、PQdescribePreparedと同じように扱われます。 PQdescribePrepared同様、2.0プロトコル接続では動作しません。

PQsendDescribePortal

指定したポータルに関する情報入手要求を送信します。完了まで待機しません。

int PQsendDescribePortal(PGconn *conn, const char *portalName);

これはPQdescribePortalの非同期版です。 要求の受付けが可能であれば1が返されます。不可能であれば0が返されます。 呼び出しに成功した後、PQgetResultを呼び出して結果を入手してください。 この関数のパラメータは、PQdescribePortalと同じように扱われます。 PQdescribePortal同様、2.0プロトコル接続では動作しません。

PQgetResult

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

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メッセージ( 項29.7を参照)を検出することもできます。

また、PQsendQuery/PQgetResultを使用するクライアントは、サーバで処理中のコマンドに対してキャンセルを試行することができます。 項29.5を参照してください。 しかし、PQcancelの戻り値と関係なく、アプリケーションはPQgetResultを使用した通常の結果読み取り手順を続けなければなりません。 キャンセル手続きの成功は単に、そのコマンドを通常よりも早めに終わらせるだけです。

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

PQsetnonblocking

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

int PQsetnonblocking(PGconn *conn, int arg);

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

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を返した後は、ソケットの読み込み準備が整うまで待ち、上述のように応答を読み取ってください。