32.4. Асинхронная обработка команд#

32.4. Асинхронная обработка команд

32.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 - это получение больших результатов запроса по одной строке за раз. Об этом рассказывается в разделе Раздел 32.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 (см. Раздел 32.9).

Клиент, использующий PQsendQuery/PQgetResult, также может попытаться отменить команду, которая все еще обрабатывается сервером; см. Раздел 32.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, дождитесь, пока сокет будет готов для чтения, а затем прочитайте ответ, как описано выше.