27.4. 異步命令處理

PQexec 函數對普通的同步應用裡提交命令已經是足夠用的了。 但是它卻有幾個主要的缺陷,而這些缺陷可能對某些用戶很重要:

不想受到這些限制的應用可以改用下面的函數,這些函數也是構造 PQexec 的函數: 也有 PQsendQueryParamsPQsendPreparePQsendQueryPrepared, 他們可以和 PQgetResult 一起使用,分別用於複製 PQexecParamsPQpreparePQexecPrepared 的功能。

PQsendQuery

向伺服器提交一個命令而不等待結果。 如果查詢成功發送則返回 1,否則返回 0。 (此時,可以用PQerrorMessage獲取關於失敗的訊息)。

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

在成功調用 PQsendQuery後,調用 PQgetResult 一次或者多次獲取結果。 在 PQgetResult 返回 NULL 指針,資料表明命令完成之前, 我們不能再調用 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 版本的協議連接上工作,並且它只允許在查詢字串裡出現一條命令。

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 版本的協議連接上跑。

PQgetResult

等待從前面 PQsendQueryPQsendQueryParamsPQsendPrepare, 或者 PQsendQueryPrepared 調用返回的下一個結果, 然後返回之。當命令結束並且沒有更多結果後返回 NULL。

PGresult *PQgetResult(PGconn *conn);

必須重複的調用 PQgetResult ,直到它返回 NULL, 資料表明該命令結束。(如果在沒有活躍的命令時調用, PQgetResult 將只是立即返回一個空指針。) 每個 PQgetResult 返回的非 NULL 結果都應該用前面描述的 PGresult 訪問函數進行分析。 不要忘了在結束分析後用 PQclear 釋放每個結果對象。 注意,PQgetResult 只是在有一個命令是活躍的而且必須的返回資料還沒有被 PQconsumeInput 讀取時阻塞。

使用 PQsendQueryPQgetResult 解決了 PQexec的一個問題: 如果一個命令字元串包含多個 SQL 命令, 這些命令的結果可以獨立的獲得。(順便說一句:這樣就允許一種簡單的重疊處理模式, 客戶端可以處理一個命令的結果而伺服器可以仍然在處理同一命令字元串的後面的查詢。) 但是,調用 PQgetResult 將仍然導致前端被阻塞住直到伺服器完成下一個 SQL 命令。這一點可以透過合理的使用下面兩個函數來避免:

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,否則忙狀態將永遠不會消除。

一個使用這些函數的典型的應用將有一個主循環使用 select() 等待所有它必須處理的條件。其中一個條件將會是伺服器來的資料已準備好, 從 select() 的角度來看就是 PQsocket 標識的文件描述符上已經有可讀取的資料。 當主循環偵測到輸入準備好,它將調用PQconsumeInput讀取輸入。 然後可以調用 PQisBusy 返回 false (0)後面可以跟著 PQgetResult。同樣它(用戶應用)可以調用 PQnotifies檢測NOTIFY訊息(參閱下面的 Section 27.7)。

一個使用 PQsendQuery/PQgetResult 的客戶端同樣也可以試圖取消一個正在被伺服器處理的命令。參閱 Section 27.5。 但是,不管 PQcancel 返回的值是多少,應用都必須使用 PQgetResult 進行正常的讀取結果的動作序列。 一次成功的取消只會導致命令比正常情況下快些結束。

透過使用上面描述的函數,我們可以避免在等待來自資料庫伺服器的時候的阻塞。 不過,應用還是有可能阻塞在給伺服器發送輸出上。這種情況比較少見,但是也可能發生, 尤其是我們要發送非常長的 SQL 命令或者資料值的時候。 (不過,最有可能的是在應用透過 COPY IN 發送資料的時候。) 為了避免這個可能性,實現完全的非阻塞資料庫操作,我們可以使用下列額外的函數。

PQsetnonblocking

把連接的狀態設置為非阻塞。

int PQsetnonblocking(PGconn *conn, int arg);

如果arg為 1,把連接狀態設置為非阻塞, 如果arg為 0, 把連接狀態設置為阻塞。如果 OK 返回 0,如果錯誤返回 -1。

在非阻塞狀態,調用 PQputlinePQputnbytesPQsendQueryPQendcopy 的時候不被阻塞, 而是在如果需要再次調用它們時將返回一個錯誤。

請注意 PQexec 不會在意任何非阻塞模式; 如果調用了 PQexec,那麼它的行為總是阻塞的。

PQisnonblocking

返回資料庫連接的阻塞狀態。

int PQisnonblocking(const PGconn *conn);

如果連接設置為非阻塞狀態,返回 1,如果是阻塞狀態返回 0。

PQflush

試圖把任何正在排隊的資料沖刷到伺服器, 如果成功(或者發送隊列為空)返回 0,如果因某種原因失敗返回 -1, 或者是在無法把發送隊列中的所有資料都發送出去,返回 1。 (這種情況只有在連接不為阻塞模式的時候才會出現)。

int PQflush(PGconn *conn);

在一個非阻塞的連接上發送任何命令或者資料之後, 調用PQflush 。 如果返回 1,就等待套接字寫準備好然後再次調用;重複這個操作直到它返回 0。 一旦PQflush返回 0,則等待套接字為讀準備好,準備好之後就像上面那樣讀取。