31.4. Асинхронная обработка команд#
31.4. Асинхронная обработка команд #
Функция PQexec
является достаточной для отправки команд в нормальных синхронных приложениях. Однако у нее есть несколько недостатков, которые могут быть важными для некоторых пользователей:
PQexec
ожидает завершения команды. Приложение может иметь другую работу (например, поддержку пользовательского интерфейса), в таком случае оно не будет блокироваться, ожидая ответа.Поскольку выполнение клиентского приложения приостанавливается, пока оно ожидает результата, приложению сложно решить, что оно хотело бы попытаться отменить выполняющуюся команду. (Это можно сделать из обработчика сигналов, но иначе нельзя).
PQexec
может вернуть только одну структуруPGresult
. Если отправленная команда содержит несколько команд SQL, все, кроме последней структурыPGresult
, будут отброшены функциейPQexec
.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
должен вызываться повторно, пока он не вернет указатель на null, что указывает на то, что команда выполнена. (Если вызвано, когда нет активной команды,PQgetResult
просто вернет указатель на null сразу же). Каждый ненулевой результат изPQgetResult
должен быть обработан с использованием тех же функций доступа кPGresult
, описанных ранее. Не забудьте освободить каждый объект результата с помощьюPQclear
, когда закончите с ним. Обратите внимание, чтоPQgetResult
будет блокироваться только в том случае, если команда активна и необходимые данные ответа еще не были прочитаны с помощьюPQconsumeInput
.В режиме конвейера
PQgetResult
будет возвращать результат нормально, если не произойдет ошибка; для любого последующего запроса, отправленного после того, который вызвал ошибку, до (и исключая) следующей точки синхронизации, будет возвращен специальный результат типаPGRES_PIPELINE_ABORTED
, и после него будет возвращен нулевой указатель. Когда будет достигнута точка синхронизации конвейера, будет возвращен результат типаPGRES_PIPELINE_SYNC
. Результат следующего запроса после точки синхронизации следует сразу (то есть после точки синхронизации нулевой указатель не возвращается).Примечание
Даже когда
PQresultStatus
указывает на критическую ошибку,PQgetResult
должен быть вызван до тех пор, пока он не вернет нулевой указатель, чтобы позволить libpq полностью обработать информацию об ошибке.
Использование PQsendQuery
и PQgetResult
решает одну из проблем PQexec
: если строка команды содержит несколько команд SQL, результаты этих команд можно получить индивидуально. (Кстати, это позволяет простую форму перекрывающейся обработки: клиент может обрабатывать результаты одной команды, пока сервер все еще работает над последующими запросами в той же строке команды).
Еще одна часто желаемая функция, которую можно получить с помощью PQsendQuery
и PQgetResult
- это получение больших результатов запроса по одной строке за раз. Об этом рассказывается в разделе Раздел 31.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
, а затем
PQgetResult
, если PQisBusy
возвращает false (0). Он также может вызвать функцию PQnotifies
для обнаружения сообщений NOTIFY
(см. Раздел 31.9).
Клиент, использующий PQsendQuery
/PQgetResult
, также может попытаться отменить команду, которая все еще обрабатывается сервером; см. Раздел 31.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, дождитесь, пока сокет будет готов для чтения, а затем прочитайте ответ, как описано выше.