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

Функция 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 не вернет пустой указатель, демонстрируя, что команда завершилась.

В конвейерном режиме запрещены командные строки, содержащие более одной команды SQL.

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 вызвать, когда нет ни одной активной команды, она просто сразу вернет пустой указатель.) Каждый отличный от NULL результат, полученный от функции PQgetResult, должен обрабатываться с помощью тех же самых описанных выше функций доступа к PGresult. Не забывайте освобождать с помощью функции PQclear память, занятую каждым результирующим объектом, по окончании работы с ним. Обратите внимание, что функция PQgetResult заблокируется, только если команда активна, а требуемые ответные данные еще не были прочитаны функцией PQconsumeInput.

В конвейерном режиме PQgetResult будет возвращать результат как обычно, если только не произойдет ошибка; для всех последующих запросов, отправленных после запроса, вызвавшего ошибку, до следующей точки синхронизации (но исключая ее) будет возвращаться специальный результат PGRES_PIPELINE_ABORTED, а после него — пустой указатель. По достижении точки синхронизации конвейера будет возвращен результат типа PGRES_PIPELINE_SYNC. Результат следующего запроса после точки синхронизации будет выдан немедленно (то есть после точки синхронизации пустой указатель не возвращается.)

Примечание
Даже когда функция PQresultStatus отражает критическую ошибку, PQgetResult все равно следует вызывать, пока она не вернет пустой указатель, чтобы позволить libpq полностью обработать информацию об ошибке.

Использование функций PQsendQuery и PQgetResult решает одну из проблем функции PQexec: если командная строка содержит несколько команд SQL, результаты этих команд можно получить по отдельности. (Кстати, это позволяет применить простую форму совмещенной обработки: клиент может обрабатывать результаты одной команды, пока сервер еще работает с более поздними запросами в той же командной строке.)

Еще одной часто необходимой функциональностью, которую можно получить с помощью функций PQsendQuery и PQgetResult, является извлечение больших результатов запроса по одной строке за раз. Это рассматривается в разделе Построчное извлечение результатов запроса.

Сам по себе вызов функции 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 (раздел Асинхронное уведомление).

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