35.4. Асинхронная обработка команд
Функция PQexec
хорошо подходит для отправки команд серверу в нормальных, синхронных приложениях. Однако она имеет ряд недостатков, которые могут иметь значение для некоторых пользователей:
PQexec
ожидает завершения выполнения команды. Приложение может иметь и другую работу, которую нужно делать (такую, как поддержание пользовательского интерфейса), в таком случае оно не захочет блокироваться, ожидая ответа.Поскольку выполнение клиентского приложения приостанавливается, пока оно ожидает результата, то приложению трудно решить, что оно хотело бы попытаться отменить выполняющуюся команду. (Это можно сделать из обработчика сигнала, но никак иначе.)
PQexec
может возвратить только одну структуруPGresult
. Если отправленная серверу строка-команда содержит множественные SQL-команды, то все структурыPGresult
, кроме последней, отбрасываются функциейPQexec
.PQexec
всегда собирает все результаты выполнения команды, буферизуя их в единственной структуреPGresult
. В то время как для приложения это упрощает логику обработки ошибок, это может быть непрактично, когда результат содержит много строк.
Приложения, которым эти ограничения не подходят, могут вместо PQexec
использовать базовые функции, на основе которых и построена функция 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
, данная функция не будет работать при подключениях по протоколу версии 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.-
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
Ожидает получения следующего результата после предшествующего вызова
PQsendQuery
,PQsendQueryParams
,PQsendPrepare
,PQsendQueryPrepared
,PQsendDescribePrepared
илиPQsendDescribePortal
и возвращает его. Когда команда завершена и результатов больше не будет, тогда возвращается нулевой указатель.PGresult *PQgetResult(PGconn *conn);
Функция
PQgetResult
должна вызываться повторно до тех пор, пока она не вернёт нулевой указатель, означающий, что команда завершена. (Если она вызвана, когда нет ни одной активной команды, тогдаPQgetResult
просто возвратит нулевой указатель сразу же.) Каждый ненулевой результат, полученный отPQgetResult
, должен обрабатываться с помощью тех же самых функций доступа к структуреPGresult
, которые были описаны выше. Не забывайте освобождать память, занимаемую каждым результирующим объектом, с помощью функцииPQclear
, когда работа с этим объектом закончена. Обратите внимание, чтоPQgetResult
заблокируется, только если какая-либо команда активна, а необходимые ответные данные ещё не были прочитаны функциейPQconsumeInput
.Примечание
Даже когда
PQresultStatus
показывает критическую ошибку, всё равно следует вызывать функциюPQgetResult
до тех пор, пока она не возвратит нулевой указатель, чтобы позволить libpq полностью обработать информацию об ошибке.
Использование PQsendQuery
и PQgetResult
решает одну из проблем PQexec
: если строка команды содержит несколько SQL-команд, то результаты каждой из них можно получить индивидуально. (Между прочим, это позволяет организовать частичное совмещение обработки: клиент может обрабатывать результаты одной команды, в то время как сервер ещё работает с более поздними запросами, содержащимися в той же самой строке-команде.)
Ещё одной часто требующейся функциональной возможностью, которую можно получить с помощью PQsendQuery
и PQgetResult
, является извлечение больших выборок по одной строке за раз. Это обсуждается в Разделе 35.5.
Сам по себе вызов PQgetResult
всё же заставит клиента заблокироваться до те пор, пока сервер не завершит выполнение следующей SQL-команды. Этого можно избежать с помощью надлежащего использования ещё двух функций:
-
PQconsumeInput
Если сервер готов передать данные, принять их.
int PQconsumeInput(PGconn *conn);
PQconsumeInput
обычно возвращает 1, показывая, что «ошибки нет», но возвращает 0, если имела место какая-либо проблема (в таком случае можно обратиться к функцииPQerrorMessage
за уточнением). Обратите внимание, что результат не говорит, были ли какие-либо входные данные фактически собраны. После вызова функцииPQconsumeInput
приложение может проверитьPQisBusy
и/илиPQnotifies
, чтобы посмотреть, не изменилось ли их состояние.PQconsumeInput
можно вызвать, даже если приложение ещё не готово иметь дело с результатом или уведомлением. Функция прочитает доступные данные и сохранит их в буфере, при этом обрабатывая условие готовности к чтению функцииPQselect()
. Таким образом, приложение может использоватьPQconsumeInput
, чтобы немедленно обработать это состояниеPQselect()
, а изучать результаты позже.-
PQisBusy
Возвращает 1, если команда занята работой, то есть функция
PQgetResult
была бы заблокирована в ожидании ввода. Возвращаемое значение 0 показывает, что функцияPQgetResult
при её вызове гарантированно не будет заблокирована.int PQisBusy(PGconn *conn);
Функция
PQisBusy
сама не будет пытаться прочитать данные с сервера; следовательно, сначала нужно вызватьPQconsumeInput
, иначе состояние занятости никогда не прекратится.
В типичном приложении, использующем эти функции, будет главный цикл, в котором PQselect()
служит для организации ожидания наступления всех условий, на которые цикл должен отвечать. Одним из условий будет наличие ввода, доступного от сервера, что в терминах функции PQselect()
означает наличие данных, готовых для чтения через файловый дескриптор, идентифицируемый с помощью PQsocket
. Когда главный цикл обнаруживает ввод, готовый для чтения, он должен вызвать PQconsumeInput
, чтобы прочитать этот ввод. Затем он может вызвать PQisBusy
, а после неё уже PQgetResult
, если PQisBusy
возвратит ложное значение (0). Главный цикл может также вызвать PQnotifies
, чтобы обнаружить сообщения NOTIFY
(см. Раздел 35.8).
Клиент, который использует PQsendQuery
/PQgetResult
, может также попытаться отменить команду, которая всё ещё обрабатывается сервером; см. Раздел 35.6. Но, независимо от возвращаемого значения функции 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, подождите, пока сокет не станет готовым к чтению, а затем прочитайте ответ, как описано выше.