33.4. Асинхронная обработка команд
Функция PQexec хорошо подходит для отправки команд серверу в нормальных, синхронных приложениях. Однако она имеет ряд недостатков, которые могут иметь значение для некоторых пользователей:
PQexecожидает завершения выполнения команды. Однако приложение может быть занято чем-то другим (например, обрабатывать активность в пользовательском интерфейсе), в таком случае блокировка приложения в ожидании ответа будет нежелательной.Поскольку выполнение клиентского приложения приостанавливается, пока оно ожидает результата, то приложению трудно решить, что оно хотело бы попытаться отменить выполняющуюся команду. (Это можно сделать из обработчика сигнала, но никак иначе.)
PQexecможет возвратить только одну структуруPGresult. Если отправленная серверу командная строка содержит несколько SQL-команд, функцияPQexecотбрасывает все результатыPGresult, кроме последнего.PQexecвсегда собирает все результаты выполнения команды, буферизуя их в единственной структуреPGresult. В то время как для приложения это упрощает логику обработки ошибок, это может быть непрактично, когда результат содержит много строк.
Приложения, которым эти ограничения не подходят, могут вместо PQexec использовать функции, на которых она базируется, PQsendQuery и PQgetResult. Есть также функции PQsendQueryParams, PQsendPrepare, PQsendQueryPrepared, PQsendDescribePrepared и PQsendDescribePortal, которые в сочетании с PQgetResult действуют аналогично функциям PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared и PQdescribePortal, соответственно.
PQsendQueryОтправляет команду серверу, не ожидая получения результата. Если команда была отправлена успешно, то функция возвратит значение 1, в противном случае она возвратит 0 (тогда нужно воспользоваться функцией
PQerrorMessageдля получения дополнительной информации о сбое).int PQsendQuery(PGconn *conn, const char *command);
После успешного вызова
PQsendQueryвызовитеPQgetResultодин или несколько раз, чтобы получить результаты. ФункциюPQsendQueryнельзя вызвать повторно (на том же самом соединении) до тех пор, покаPQgetResultне вернёт нулевой указатель, означающий, что выполнение команды завершено.В конвейерном режиме эту функцию вызывать нельзя.
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она позволяет включить только одну команду в строку запроса.PQsendPrepareПосылает запрос на создание подготовленного оператора с данными параметрами и не дожидается завершения его выполнения.
int PQsendPrepare(PGconn *conn, const char *stmtName, const char *query, int nParams, const Oid *paramTypes);Это асинхронная версия функции
PQprepare. Она возвращает 1, если ей удалось отправить запрос, и 0 в противном случае. После её успешного вызова следует вызвать функциюPQgetResult, чтобы определить, создал ли сервер подготовленный оператор. Эта функция обрабатывает свои параметры точно так же, как и функцияPQprepare.PQsendQueryPreparedПосылает запрос на выполнение подготовленного оператора с данными параметрами, не ожидая получения результата.
int PQsendQueryPrepared(PGconn *conn, const char *stmtName, int nParams, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat);Эта функция подобна функции
PQsendQueryParams, но выполняемую команду определяет не текст запроса, а ссылка на предварительно подготовленный оператор. Эта функция обрабатывает свои параметры точно так же, как и функцияPQexecPrepared.PQsendDescribePreparedОтправляет запрос на получение информации об указанном подготовленном операторе и не дожидается завершения выполнения запроса.
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
Это асинхронная версия функции
PQdescribePrepared. Она возвращает 1, если ей удалось отправить запрос, и 0 в противном случае. После её успешного вызова следует вызвать функциюPQgetResultдля получения результата. Эта функция обрабатывает свои параметры точно так же, как и функцияPQdescribePrepared.PQsendDescribePortalОтправляет запрос на получение информации об указанном портале и не дожидается завершения выполнения запроса.
int PQsendDescribePortal(PGconn *conn, const char *portalName);
Это асинхронная версия функции
PQdescribePortal. Она возвращает 1, если ей удалось отправить запрос, и 0 в противном случае. После её успешного вызова следует вызвать функциюPQgetResultдля получения результата. Эта функция обрабатывает свои параметры точно так же, как и функцияPQdescribePortal.PQgetResultОжидает получения следующего результата после предшествующего вызова
PQsendQuery,PQsendQueryParams,PQsendPrepare,PQsendQueryPrepared,PQsendDescribePrepared,PQsendDescribePortalилиPQpipelineSyncи возвращает его. Когда команда завершена и результатов больше не будет, возвращается нулевой указатель.PGresult *PQgetResult(PGconn *conn);
Функция
PQgetResultдолжна вызываться повторно до тех пор, пока она не вернёт нулевой указатель, означающий, что команда завершена. (Если она вызвана, когда нет ни одной активной команды, тогдаPQgetResultпросто возвратит нулевой указатель сразу же.) Каждый ненулевой результат, полученный отPQgetResult, должен обрабатываться с помощью тех же самых функций доступа к структуреPGresult, которые были описаны выше. Не забывайте освобождать память, занимаемую каждым результирующим объектом, с помощью функцииPQclearкогда работа с этим объектом закончена. Обратите внимание, чтоPQgetResultзаблокируется, только если какая-либо команда активна, а необходимые ответные данные ещё не были прочитаны функциейPQconsumeInput.В конвейерном режиме
PQgetResultбудет работать как обычно, если не произойдёт ошибка; для любого запроса, отправленного после запроса, вызвавшего ошибку, до следующей точки синхронизации (но не включая её), будет возвращаться специальный результат типаPGRES_PIPELINE_ABORTED, а после него — нулевой указатель. При достижении точки синхронизации конвейера будет возвращён результат типаPGRES_PIPELINE_SYNC. Результат следующего запроса после точки синхронизации поступит немедленно (то есть после точки синхронизации нулевой указатель не возвращается).Примечание
Даже когда
PQresultStatusпоказывает критическую ошибку, всё равно следует вызывать функциюPQgetResultдо тех пор, пока она не возвратит нулевой указатель, чтобы позволить libpq полностью обработать информацию об ошибке.
Использование PQsendQuery и PQgetResult решает одну из проблем PQexec: если строка запроса содержит несколько SQL-команд, то результаты каждой из них можно получить индивидуально. (Между прочим, это позволяет организовать частичное совмещение обработки: клиент может обрабатывать результаты одной команды, в то время как сервер ещё работает с более поздними запросами, содержащимися в той же строке.)
Ещё одной часто требующейся функциональной возможностью, которую можно получить с помощью PQsendQuery и PQgetResult, является извлечение больших выборок по одной строке за раз. Это обсуждается в Разделе 33.6.
Сам по себе вызов 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() или poll() организуется ожидание всех условий, на которые нужно реагировать в этом цикле. Одним из условий будет поступление на вход данных от сервера, что в терминах функции select() означает наличие данных, которые можно прочитать через файловый дескриптор, выдаваемый функцией PQsocket. Когда в главном цикле обнаруживаются готовые входные данные, следует вызвать PQconsumeInput , чтобы прочитать их. После этого можно вызвать PQisBusy, и если в результате получен 0 (что свидетельствует о свободном состоянии), затем вызвать PQgetResult. В главном цикле также можно вызвать PQnotifies, чтобы проверить сообщения NOTIFY (см. Раздел 33.9).
Клиент, который использует PQsendQuery/PQgetResult, может также попытаться отменить команду, которая всё ещё обрабатывается сервером; см. Раздел 33.7. Но независимо от возвращаемого значения функции PQcancel, приложение должно продолжать обычную последовательность операций чтения результатов запроса, вызывая PQgetResult. В случае успешной отмены команда просто завершится раньше, чем она завершилась бы, выполняясь до конца.
Используя функции, описанные выше, можно избежать блокирования, ожидая ввода от сервера баз данных. Однако всё же возможно, что приложение будет заблокировано в ожидании отправки вывода на сервер. Это бывает относительно нечасто, но может иметь место, если отправлены очень длинные SQL-команды или значения данных. (Однако это значительно более вероятно, если приложение отправляет данные через команду COPY IN.) Чтобы предотвратить эту возможность и достичь совершенно неблокирующего режима работы с базой данных, можно использовать следующие дополнительные функции.
PQsetnonblockingУстанавливает неблокирующий статус подключения.
int PQsetnonblocking(PGconn *conn, int arg);
Устанавливает состояние подключения как неблокирующее, если
argравен 1, или блокирующее, еслиargравен 0. Возвращает 0 в случае успешного завершения и -1 в случае ошибки.В неблокирующем состоянии успешные вызовы
PQsendQuery,PQputline,PQputnbytes,PQputCopyDataиPQendcopyне блокируются; изменения сохраняются в локальном буфере вывода до тех пор, пока не будут сброшены. Неудачные вызовы возвращают ошибку, и их придётся повторить.Обратите внимание, что функция
PQexecне соблюдает неблокирующий режим. Если она вызывается, она всё равно работает в блокирующем режиме.PQisnonblockingВозвращает режим блокирования для подключения базы данных.
int PQisnonblocking(const PGconn *conn);
Возвращает 1, если подключение установлено в неблокирующем режиме, и 0, если режим блокирующий.
PQflushПытается сбросить любые выходные данные, стоящие в очереди, на сервер. Возвращает 0 в случае успеха (или если очередь на отправку пуста), -1 в случае сбоя по какой-либо причине или 1, если она ещё не смогла отправить все данные, находящиеся в очереди (этот случай может иметь место, только если соединение неблокирующее).
int PQflush(PGconn *conn);
После отправки любой команды или данных через неблокирующее подключение следует вызвать функцию PQflush. Если она возвратит 1, подождите, пока сокет станет готовым к чтению или записи. Если он станет готовым к записи, снова вызовите PQflush. Если он станет готовым к чтению, вызовите PQconsumeInput , а затем вновь вызовите PQflush. Повторяйте до тех пор, пока PQflush не возвратит 0. (Необходимо выполнять проверку на состояние готовности к чтению и забирать входные данные с помощью PQconsumeInput , потому что сервер может заблокироваться, пытаясь отправить нам данные, например, сообщения NOTICE, и не будет читать наши данные до тех пор, пока мы не прочитаем его.) Как только PQflush возвратит 0, подождите, пока сокет не станет готовым к чтению, а затем прочитайте ответ, как описано выше.