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, подождите, пока сокет не станет готовым к чтению, а затем прочитайте ответ, как описано выше.