27.4. 異步命令處理

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

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

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 版本的協議連接上工作,並且它只允許在查詢字串裡出現一條命令。

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

等待從前面 PQsendQueryPQsendQueryParams, 或者 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.6)。

一個使用 PQsendQuery/PQgetResult 的客戶端同樣也可以試圖取消一個正在被服務器處理的命令。

PQrequestCancel

要求服務器放棄處理當前命令。

int PQrequestCancel(PGconn *conn);

如果取消的請求成功發送,返回值是 1,否則是 0。 (如果為假,PQerrorMessage 會告之為什麼。) 不過,取消請求的成功發送將不保證請求將產生作用。不管 PQrequestCancel 的返回值是什麼,應用都必須繼續使用 PQgetResult進行通常的後續的結果讀取工作。 如果取消動作生效,當前的命令將提前退出並返回一個錯誤結果。 如果取消動作失敗(也就是服務器已經處理完命令了),那麼將沒有可見的結果。

注意:如果當前的命令是事務的一部分,取消動作將退出整個事務。

PQrequestCancel 可以很安全地從一個信號句柄裡調用。 所以,如果取消動作可以從信號句柄裡發出的話,它也可以與簡單的 PQexec一起使用。例如, psql 從一個SIGINT信號句柄裡調用 PQrequestCancel ,因此允許交互地取消通過 PQexec 發出的查詢。

通過使用上面描述的函數,我們可以避免在等待來自數據庫服務器的時候的阻塞。 不過,應用還是有可能阻塞在給服務器發送輸出上。這種情況比較少見,但是也可能發生, 尤其是我們要發送非常長的 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,則等待套接字為讀準備好,準備好之後就像上面那樣讀取。