Клиент-серверный протокол

Для взаимодействия между внешним и внутренним интерфейсом (клиентами и серверами) в QHB используется протокол, основанный на сообщениях. Этот протокол поддерживается через TCP/IP, а также через сокеты домена Unix. В качестве стандартного номера TCP-порта для серверов, поддерживающих этот протокол, в IANA был зарегистрирован номер 5432, но на практике можно использовать любой порт, не требующий специальных прав.

В этом документе описывается версия 3.0 данного протокола, реализованная в последних версиях QHB. Один сервер может поддерживать несколько версий протокола. Сообщение о запуске при установке подключения говорит серверу, какую версию протокола пытается использовать клиент. Если основная версия, запрашиваемая клиентом, не поддерживается сервером, подключение будет отклонено (например, это произойдет, если клиент запросит протокол версии 4.0, которая еще не существует на момент написания этой главы). Если сервером не поддерживается запрашиваемая клиентом дополнительная версия (например, клиент запрашивает версию 3.1, а сервер поддерживает только 3.0), сервер может либо сбросить соединение, либо ответить сообщением NegotiateProtocolVersion, где будет указана наибольшая дополнительная версия протокола, которую он поддерживает. Затем клиент может решить либо продолжить установку подключения с указанной версией протокола, либо разорвать соединение.

Чтобы эффективно обслуживать несколько клиентов, сервер запускает для каждого из них новый «внутренний» процесс. В текущей реализации новый дочерний процесс запускается сразу после обнаружения входящего подключения. Однако это происходит прозрачно для протокола. Применительно к протоколу термины «внутренний интерфейс» и «сервер» взаимозаменяемы, равно как и «внешний/пользовательский интерфейс» и «клиент».



Обзор

В протоколе имеются отдельные фазы для запуска и обычных операций. В фазе запуска клиент устанавливает соединение с сервером и аутентифицируется, чтобы удовлетворить сервер. (Это может потребовать одного или нескольких сообщений в зависимости от используемого метода аутентификации.) Если все проходит успешно, сервер передает клиенту информацию о текущем состоянии и наконец переходит к обычным операциям. За исключением сообщения о запуске при установке подключения, эта часть протокола управляется сервером.

Во время обычной работы клиент посылает серверу запросы и другие команды, а сервер возвращает результаты запросов и другие ответы. Существует несколько сценариев (например, NOTIFY), когда сервер передает сообщения по собственной инициативе, но по большей части эта фаза сеанса управляется запросами клиента.

Завершение сеанса обычно происходит по решению клиента, но в некоторых случаях может быть принудительно вызвано сервером. В любом случае, когда сервер закрывает соединение, перед этим он откатывает любую открытую (незавершенную) транзакцию.

В ходе обычной работы команды SQL могут выполняться по одному из двух подчиненных протоколов. По протоколу «простого запроса» клиент просто посылает текстовую строку запроса, которая анализируется и немедленно выполняется сервером. По протоколу «расширенного запроса» обработка запросов разделяется на несколько этапов: анализ, привязка значений параметров и выполнение. Это дает повышенную гибкость и производительность за счет увеличения сложности.

Для обычной работы имеются также дополнительные подчиненные протоколы для специальных операций вроде COPY.


Обзор обмена сообщениями

Все взаимодействие происходит через поток сообщений. Первый байт сообщения определяет его тип, а следующие четыре — длину остального сообщения (этот счетчик длины включает размер самого поля длины, но не байт с типом сообщения). Остальное содержимое сообщения определяется его типом. По историческим причинам у самого первого сообщения, передаваемого клиентом (сообщения о запуске), первого байта с типом сообщения нет.

Чтобы не потерять синхронизацию в потоке сообщений, серверы и клиенты обычно считывают все сообщение в буфер (используя счетчик байтов), прежде чем пытаться обработать его содержимое. Это позволяет с легкостью восстановить работу, если при обработке содержимого обнаруживается ошибка. В экстремальных ситуациях (например, при нехватке памяти для буферизации сообщения), получатель может воспользоваться счетчиком байтов для определения того, какой объем данных нужно пропустить, прежде чем продолжить считывать сообщения.

С другой стороны, клиенты и серверы ни при каких условиях не должны передавать неполные сообщения. Обычно для этого перед началом отправки все сообщение маршализуется в буфере. Если в процессе отправки или получения сообщения происходит сбой передачи, единственной разумной реакцией будет прерывание соединения, поскольку вероятность восстановления синхронизации по границам сообщений крайне мала.


Обзор расширенных запросов

В протоколе расширенных запросов выполнение команд SQL разделено на несколько этапов. Состояние между этапами представляется двумя типами объектов: подготовленными операторами и порталами. Подготовленный оператор представляет собой результат синтаксического и семантического анализов текстовой строки запроса. Сам по себе подготовленный оператор не готов для выполнения, потому что в нем могут отсутствовать конкретные значения для параметров. Портал представляет собой готовый к выполнению или уже частично выполненный оператор, в котором заданы все недостающие значения параметров. (Для операторов SELECT портал равнозначен открытому курсору, но мы предпочитаем использовать другой термин, поскольку курсоры неприменимы к операторам, отличным от SELECT.)

Общий цикл выполнения состоит из этапа синтаксического анализа, на котором из текстовой строки создается подготовленный оператор, этапа привязки, на котором из подготовленного оператора и значений всех необходимых параметров создается портал, и этапа выполнения, на котором выполняется запрос портала. В случае запроса, возвращающего строки (SELECT, SHOW и т. д.), можно указать, чтобы на этапе выполнения извлекалось лишь ограниченное число строк, поэтому для завершения операции может понадобиться несколько этапов выполнения.

Сервер может отслеживать несколько подготовленных операторов и порталов (но обратите внимание, что они существуют только в рамках сеанса и никогда не разделяются между сеансами). К существующим подготовленным операторам и порталам обращаются по именам, назначенным им при создании. Кроме того, существуют и «безымянные» подготовленные операторы и порталы. Хотя они ведут себя практически так же, как именованные объекты, операции с ними оптимизированы для разового выполнения запроса с последующим их освобождением, тогда как операции с именованными объектами оптимизированы в расчете на многократное использование.


Форматы и коды форматов

Данные определенного типа могут передаваться в одном из нескольких различных форматов. На данный момент в QHB поддерживаются только текстовый «text» и двоичный «binary» форматы, но в протоколе предусмотрены возможности для расширения в будущем. Желаемый формат для любого значения задается кодом формата. Клиенты могут указывать код формата для каждого передаваемого значения параметра и для каждого столбца результата запроса. Текстовый формат имеет код ноль, двоичный — один, а все остальные коды форматов зарезервированы для определения в будущем.

Текстовым представлением значения являются строки, которые выдаются и принимаются функциями преобразования ввода/вывода для определенного типа данных. В передаваемом представлении отсутствует завершающий пустой символ; клиент должен добавить его к получаемым значениям, если хочет обрабатывать их как строки C. (Кстати говоря, данным в текстовом формате нельзя содержать пустые символы.)

В двоичных представлениях для целых чисел используется сетевой порядок байтов (наиболее значащий байт идет первым). Какое двоичное представление имеют другие типы данных, можно узнать в документации или исходном коде. Учтите, что двоичные представления для сложных типов данных могут изменяться от версии к версии сервера; текстовый формат более платформонезависимый.



Поток сообщений

В этом разделе описывается поток сообщений и семантика каждого типа сообщений. (Подробная информация о точном представлении каждого сообщения находится в разделе Форматы сообщений.) В зависимости от состояния соединения выделяется несколько различных подчиненных протоколов: запуск, запрос, вызов функции, копирование (COPY) и завершение. Также существуют специальные средства для асинхронных операций (включая отклики на уведомления и отмены команд), которые могут осуществляться в любое время после фазы запуска.


Запуск

Чтобы начать сеанс, клиент открывает подключение к серверу и посылает сообщение о запуске. В это сообщение включаются имена пользователя и базы данных, к которой этот пользователь хочет подключиться; также в нем определяется, какая именно версия протокола будет использоваться. (Кроме того, сообщение о запуске может включать дополнительные значения для параметров времени выполнения.) Затем сервер применяет эту информацию и содержимое своих файлов конфигурации (например, qhb_hba.conf), чтобы определить, можно ли предварительно разрешить это подключение и какая требуется дополнительная аутентификация (если требуется).

Потом сервер передает соответствующее сообщение с запросом аутентификации, на которое клиент должен ответить соответствующим сообщением с ответом аутентификации (например, с паролем). Для всех методов аутентификации, кроме GSSAPI и SASL, будет максимум один запрос и один ответ. Для некоторых методов ответ от клиента вообще не требуется, поэтому запрос аутентификации не осуществляется. Для GSSAPI и SASL завершение аутентификации может потребовать нескольких обменов пакетами.

Цикл аутентификации заканчивает сервер, либо отклоняя попытку подключения (ErrorResponse), либо отправляя AuthenticationOk.

В этой фазе возможны следующие ответные сообщения от сервера:

ErrorResponse
Попытка подключения была отклонена. Сразу после этого сервер закрывает соединение.

AuthenticationOk
Обмен сообщениями аутентификации завершен успешно.

AuthenticationKerberosV5
Теперь клиент должен принять участие в диалоге аутентификации по протоколу Kerberos V5 (здесь он не описывается, поскольку является частью спецификации Kerberos) с сервером. Если диалог завершается успешно, сервер отвечает AuthenticationOk, иначе — ErrorResponse. Этот вариант аутентификации больше не поддерживается.

AuthenticationCleartextPassword
Теперь клиент должен послать сообщение PasswordMessage, содержащее пароль в открытом виде. Если пароль правильный, сервер отвечает AuthenticationOk, иначе — ErrorResponse.

AuthenticationMD5Password
Теперь клиент должен послать сообщение PasswordMessage, содержащее пароль (с именем пользователя) зашифрованным посредством хэша MD5, с последующим повторным шифрованием с использованием 4-байтового случайного значения соли, указанного в сообщении AuthenticationMD5Password. Если пароль правильный, сервер отвечает AuthenticationOk, иначе — ErrorResponse. Фактическое сообщение PasswordMessage можно вычислить в SQL как concat('md5', md5(concat(md5(concat(password, username)), random-salt))). (Учтите, что функция md5() возвращает результат в виде шестнадцатеричной строки.)

AuthenticationSCMCredential
Этот ответ возможно только для локальных подключений через домен Unix на платформах, поддерживающих сообщения с учетными данными SCM. Клиент должен выдать сообщение с учетными данными SCM, а затем послать один байт данных. (Содержимое этого байта данных не представляет интереса; он используется только для того, чтобы сервер ждал достаточно долго, чтобы получить сообщение с учетными данными.) Если учетные данные приемлемы, сервер отвечает AuthenticationOk, иначе — ErrorResponse. (В настоящее время этот тип сообщений считается устаревшим и в итоге может быть удален из спецификации протокола.)

AuthenticationGSS Теперь клиент должен инициировать согласование GSSAPI. В ответ на это сообщение клиент отправит сообщение GSSResponse с первой частью потока данных GSSAPI. Если потребуются дополнительные сообщения, сервер ответит AuthenticationGSSContinue.

AuthenticationGSSContinue
Это сообщение содержит данные ответа на предыдущий этап согласования GSSAPI (сообщение AuthenticationGSS или предыдущее AuthenticationGSSContinue). Если данные GSSAPI в этом сообщении указывают, что для завершения аутентификации требуются еще данные, клиент должен переслать эти данные в виде еще одного сообщения GSSResponse. Если аутентификация GSSAPI завершается этим сообщением, сервер затем отправит AuthenticationOk, извещая об успешной аутентификации, или ErrorResponse, указывая на ошибку.

AuthenticationSASL
Теперь клиент должен инициировать согласование SASL, воспользовавшись одним из механизмов SASL, перечисленных в этом сообщении. В ответ на это сообщение клиент отправит SASLInitialResponse с именем выбранного механизма и первую часть потока данных SASL. Если потребуются дополнительные сообщения, сервер ответит AuthenticationSASLContinue. Подробную информацию см. в разделе Аутентификация SASL.

AuthenticationSASLContinue
Это сообщение содержит данные вызова с предыдущего этапа согласования SASL (сообщение AuthenticationSASL или предыдущее AuthenticationSASLContinue). Клиент должен ответит сообщением SASLResponse.

AuthenticationSASLFinal
Аутентификация SASL завершилась с дополнительными специфичными для механизма данными для клиента. Затем сервер отправит AuthenticationOk извещая об успешной аутентификации, или ErrorResponse, указывая на ошибку. Это сообщение посылается, только если в механизме SASL указаны дополнительные данные, которые в завершение нужно передать от сервера клиенту.

NegotiateProtocolVersion
Сервер не поддерживает дополнительную версию протокола, запрошенную клиентом, но поддерживает более раннюю версию протокола; в этом сообщении указывается наибольшая поддерживаемая дополнительная версия. Кроме того, это сообщение будет отправлено, если клиент запросил в пакете запуска неподдерживаемые параметры протокола (т. е. начинающиеся с _pq_.). За этим сообщением должно последовать либо сообщение ErrorResponse, либо сообщение, извещающее об успехе или провале аутентификации.

Если клиент не поддерживает запрошенный сервером метод аутентификации, он должен немедленно закрыть соединение.

Получив сообщение AuthenticationOk, клиент должен ждать дальнейших сообщений от сервера. В этой фазе запускается внутренний процесс,а клиент выступает просто в качестве заинтересованного наблюдателя. Попытка запуска все еще может провалиться (ErrorResponse), или сервер может отказаться поддерживать запрошенную дополнительную версию протокола (NegotiateProtocolVersion), но в обычной ситуации сервер отправит несколько сообщений ParameterStatus, BackendKeyData и наконец ReadyForQuery.

Во время этой фазы сервер попытается применить все дополнительные значения параметров времени выполнения, полученные в сообщении о запуске. В случае успеха эти значения станут сеансовыми значениями по умолчанию. При ошибке он передаст ErrorResponse и завершится.

В этой фазе возможны следующие ответные сообщения от сервера:

BackendKeyData
Это сообщение предоставляет данные секретного ключа, которые клиент должен сохранить, если хочет впоследствии иметь возможность выполнять отмену запросов. Клиент не должен отвечать на это сообщение, но должен продолжать ожидать сообщения ReadyForQuery.

ParameterStatus
Это сообщение информирует клиента о текущих (начальных) значениях параметров сервера, например, client_encoding or DateStyle. Клиент может игнорировать это сообщение или записать значения для дальнейшего использования; более подробную информацию см. в подразделе Асинхронные операции. Клиент не должен отвечать на это сообщение, но должен продолжать ожидать сообщения ReadyForQuery.

ReadyForQuery
Запуск завершен. Теперь клиент может выполнять команды.

ErrorResponse
Запуск не удался. После передачи этого сообщения соединение закрывается.

NoticeResponse
Было выдано предупреждающее сообщение. Клиент должен отобразить это сообщение, но продолжать ожидать сообщения ReadyForQuery или ErrorResponse.

Сообщение ReadyForQuery ничем не отличается от того, которое сервер будет передавать после каждого цикла команд. В зависимости от потребностей кодирования клиента, имеет смысл рассматривать сообщение ReadyForQuery как начинающее цикл команд или как завершающее фазу запуска и каждый последующий цикл команд.


Простой запрос

Цикл простого запроса запускается клиентом, посылающим серверу сообщение Query. Это сообщение содержит команду (или команды) SQL, выраженную в виде текстовой строки. Затем сервер отправляет одно или несколько ответных сообщений, в зависимости от содержимого командной строки запроса, и в конце ответное сообщение ReadyForQuery. ReadyForQuery информирует клиента о том, что он может безопасно передавать новую команду. (На самом деле клиенту необязательно дожидаться ReadyForQuery, прежде чем послать следующую команду, но тогда он сам должен разбираться, что произошло, если предыдущая команда завершается ошибкой, а последующие выполняются успешно.)

В этой фазе возможны следующие ответные сообщения от сервера:

CommandComplete
Команда SQL завершилась нормально.

CopyInResponse
Сервер готов копировать данные, полученные от клиента, в таблицу; см. подраздел Операции COPY.

CopyOutResponse
Сервер готов копировать данные из таблицы клиенту; см. подраздел Операции COPY.

RowDescription
Показывает, что сейчас в ответ на запрос SELECT, FETCH и т. п. будут возвращены строки. В содержимом этого сообщения описывается структура столбцов этих строк. За ним для каждой строки, возвращаемой клиенту, последует сообщение DataRow.

DataRow
Одна строка из набора, возвращаемого запросом SELECT, FETCH и т. п.

EmptyQueryResponse
Была принята пустая строка запроса.

ErrorResponse
Произошла ошибка.

ReadyForQuery
Обработка строки запроса завершена. Известие об этом передается отдельным сообщением, поскольку строка запроса может содержать несколько SQL. (Сообщение CommandComplete отмечает конец обработки одной команды SQL, а не всей строки.) ReadyForQuery будет передаваться всегда, независимо от того, успешно ли завершилась обработка или с ошибкой.

NoticeResponse
Было выдано предупреждающее сообщение, касающееся запроса. Замечания дополняют другие ответы, т. е. сервер продолжит обрабатывать команду.

Ответ на запрос SELECT (или другие запросы, возвращающие наборы строк, например EXPLAIN или SHOW) обычно состоит из RowDescription, нуля и более сообщений DataRow и завершающего CommandComplete. Копирование (COPY) данных от клиента или клиенту активизирует специальный протокол, как описано в подразделе Операции COPY. Для остальных типов запросов обычно выдается только сообщение CommandComplete.

Поскольку строка запроса может содержать несколько запросов (разделенных точкой с запятой), до завершения ее обработки сервером может передаваться несколько таких ответных последовательностей. ReadyForQuery выдается, когда была обработана вся строка, и сервер готов принять новую строку запроса.

Если получена совершенно пустая (не содержащая ничего, кроме пробельных символов) строка, ответом будет EmptyQueryResponse, а затем ReadyForQuery.

В случае ошибки выдается ErrorResponse, а затем ReadyForQuery. Сообщением ErrorResponse прерывается вся дальнейшая обработка строки запроса (даже если в ней остались другие запросы). Обратите внимание, что это может произойти и в середине последовательности сообщений, сгенерированных в ответ на отдельный запрос.

В режиме простых запросов получаемые значения всегда имеют текстовый формат, за исключением результатов команды FETCH из курсора, объявленного с параметром BINARY. В этом случае получаемые значения имеют двоичный формат. На то, какой именно формат используется, указывают коды форматов, передаваемые в сообщении RowDescription.

Клиент должен быть готов принять сообщения ErrorResponse и NoticeResponse, ожидая любой другой тип сообщений. Информацию о сообщениях, которые сервер может сгенерировать в ответ на внешние события, см. в подразделе Асинхронные операции.

Рекомендуется кодировать клиенты в стиле конечного аппарата, который в любой момент, когда это имеет смысл, будет принимать сообщения любого типа, а не программировать жестко, рассчитывая на точную последовательность сообщений.


Несколько операторов в простом запросе

Когда простое сообщение Query содержит несколько операторов SQL (разделенных точкой с запятой), эти операторы выполняются в одной транзакции, если только среди них нет явных команд управления транзакциями, вызывающих другое поведение. Например, если сообщение содержит

INSERT INTO mytable VALUES(1);
SELECT 1/0;
INSERT INTO mytable VALUES(2);

то ошибка деления на ноль в SELECT приведет к откату первого INSERT. Более того, из-за прерывания выполнения этого сообщения на первой ошибке второй INSERT вообще не будет выполняться.

Если вместо этого сообщение содержит

BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELECT 1/0;

то первый INSERT фиксируется явной командой COMMIT. Второй INSERT и SELECT по-прежнему воспринимаются как одна транзакция, поэтому ошибка деления на ноль приведет к откату второго INSERT, но не первого.

Это поведение реализуется путем выполнения операторов многооператорного сообщения Query в неявном блоке транзакции, если только в сообщении нет какого-нибудь явного блока транзакции, в котором они должны выполняться. Основное различие между неявным блоком транзакции и обычным заключается в том, что неявный блок автоматически закрывается в конце сообщения Query либо путем неявной фиксации, если нет ошибок, либо путем неявного отката, если ошибка есть. Это похоже на неявную фиксацию и откат, которые происходят с оператором, выполняемым отдельно (вне блока транзакции).

Если сеанс уже находится внутри блока транзакции в результате выполнения оператора BEGIN из некоторого предыдущего сообщения, то сообщение Query просто продолжает этот блок транзакции независимо от того, содержится ли в нем один оператор или несколько. Однако если сообщение Query содержит оператор COMMIT или ROLLBACK, закрывающий существующий блок транзакции, то любые последующие операторы выполняются в неявном блоке транзакции. И наоборот, если в многооператорном сообщении Query фигурирует оператор BEGIN, он начинает обычный блок транзакции, который будет закончен только явными COMMIT или ROLLBACK независимо от того, находятся они в этом сообщении Query или в последующем. Если BEGIN следует за операторами, которые выполнялись в неявном блоке транзакции, эти операторы не фиксируются немедленно; на практике они задним числом включаются в новый обычный блок транзакции.

Оператор COMMIT или ROLLBACK, находящийся в неявном блоке транзакции, выполняется как обычно, закрывая этот неявный блок; однако при этом будет выдано предупреждение, поскольку COMMIT или ROLLBACK без предшествующего BEGIN может оказаться ошибочным. Если за этими операторами следуют другие, для них будет начат новый неявный блок транзакции.

Точки сохранения в неявном блоке транзакции не допускаются, поскольку они могут конфликтовать с политикой автоматического закрытия блока при любой ошибке.

Помните, что, независимо от присутствия любых команд управления транзакциями, выполнение сообщения Query останавливается при первой же ошибке. Таким образом, например, если задать команды

BEGIN;
SELECT 1/0;
ROLLBACK;

в одном сообщении Query, сеанс останется внутри не выполнившегося обычного блока транзакции, поскольку до ROLLBACK после ошибки деления на ноль выполнение не дошло. Чтобы восстановить сеанс до рабочего состояния потребуется выполнить еще один ROLLBACK.

Также нужно отметить, что первоначальный лексический и синтаксический анализ проводится для всей строки запроса, прежде чем выполняется какая-либо ее часть. Поэтому простые ошибки (например, опечатка в ключевом слове) в последующих операторах могут не дать выполниться предыдущим операторам. Обычно этого не видно пользователям, поскольку эти операторы все равно бы все откатились при выполнении в неявном блоке транзакции. Тем не менее это можно увидеть, если попытаться выполнить несколько транзакций в многооператорном Query. Например, если бы опечатка превратила наш предыдущий пример в

BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELCT 1/0;

ни один из операторов не выполнился бы, приведя к видимому отличию, состоящему в том, что первый INSERT не зафиксируется. Ошибки, выявленные при семантическом анализе или позже, например, опечатки в имени таблицы или столбца, такого влияния не оказывают.


Расширенный запрос

Протокол расширенных запросов разбивает вышеописанный протокол простых запросов на несколько этапов. Результаты подготовительных этапов можно неоднократно повторно использовать для улучшения эффективности. Более того, с ним доступна дополнительная функциональность, например, возможность передавать значения данных в виде отдельных параметров, вместо того чтобы вставлять из напрямую в строку запроса.

В расширенном протоколе клиент сначала передает сообщение Parse, содержащее текстовую строку запроса и, возможно, некоторую информацию о типах данных шаблонов параметров и имя целевого объекта подготовленного оператора (если строка пустая, создается безымянный подготовленный оператор). Ответом на это будет ParseComplete или ErrorResponse. Типы данных параметров указываются по OID; если они не заданы, анализатор пытается определить типы данных так же, как он делал бы для нетипизированных строковых констант.

Примечание
Тип данных параметра можно оставить неопределенным, задав для него значение ноль или создав массив с OID типов параметров короче, чем количество символов параметров ($n), используемых в строке запроса. Другой особый случай — в качестве типа параметра можно указать void (то есть OID псевдотипа void). Это сделано для того, чтобы позволить использовать символы параметров для параметров функций, в действительности являющихся выходными параметрами (OUT). Обычно параметр void невозможно использовать ни в каком контексте, но если такой символ параметра присутствует в списке параметров функции, он по сути игнорируется. К примеру, вызову такой функции, как foo($1,$2,$3,$4), может соответствовать функция с двумя входными (IN) и двумя выходными (OUT) аргументами, если аргументы $3 и $4 заданы как имеющие тип void.

Примечание
Строка запроса, содержащаяся в сообщении Parse, не может содержать больше одного оператора SQL, иначе выдается синтаксическая ошибка. Это ограничение отсутствует в протоколе простого запроса, но существует в расширенном протоколе, поскольку разрешение подготовленным операторам или порталам содержать несколько команд неоправданно усложнило бы протокол.

При успешном создании объект именованного подготовленного оператора продолжает существовать до конца текущего сеанса, если только он не будет уничтожен явно. Безымянный подготовленный оператор существует только до выполнения следующего оператора Parse, где этот безымянный оператор указан в качестве целевого. (Обратите внимание, что сообщение простого запроса тоже уничтожает безымянный оператор.) Именованные подготовленные операторы должны явно закрываться, прежде чем их сможет переопределить другое сообщение Parse, но для безымянного оператора этого не требуется. Кроме того, именованные подготовленные операторы можно создавать и вызывать на уровне команд SQL, используя PREPARE и EXECUTE.

Когда подготовленный оператор существует, его можно подготовить к выполнению с помощью сообщения Bind. В сообщении Bind задается имя исходного подготовленного оператора (пустая строка обозначает безымянный подготовленный оператор), имя целевого портала (пустая строка обозначает безымянный портал) и значения для любых шаблонов параметров, имеющихся в подготовленном операторе. Предоставляемый набор параметров должен соответствовать параметрам, необходимым подготовленному оператору. (Если в сообщении Parse вы объявили какие-либо параметры void, передайте для них значения NULL в сообщении Bind.) Bind также задает формат для данных, возвращаемых запросом; формат можно задать для всех данных или для на уровне столбцов. Ответом на это сообщение будет BindComplete или ErrorResponse.

Примечание
Выбор между текстовым и двоичным форматом вывода определяется кодами форматов, задаваемыми в Bind, вне зависимости от задействованной команды SQL. При использовании протокола расширенных запросов атрибут BINARY в объявлениях курсоров не имеет значения.

Планирование запроса обычно происходит при обработке сообщения Bind. Если у подготовленного оператора нет параметров или он выполняется многократно, сервер может сохранить созданный план и повторно использовать его при последующих сообщениях Bind для того же подготовленного оператора. Однако он будет делать это, только если обнаружит, что можно создать типовой план, эффективность которого ненамного меньше, чем у плана, зависящего от передаваемых конкретных значений параметров. Относительно протокола это происходит открыто.

При успешном создании объект именованного портала продолжает существовать до конца текущей транзакции, если только он не будет уничтожен явно. Безымянный портал уничтожается в конце транзакции или при выполнении следующего оператора Bind, где этот безымянный портал указан в качестве целевого. (Обратите внимание, что сообщение простого запроса тоже уничтожает безымянный портал.) Именованные порталы должны явно закрываться, прежде чем их сможет переопределить другое сообщение Bind, но для безымянного портала этого не требуется. Кроме того, именованные порталы можно создавать и вызывать на уровне команд SQL, используя DECLARE CURSOR и FETCH.

Когда портал существует, его можно выполнить с помощью сообщения Execute. В сообщении Execute задается имя портала (пустая строка обозначает безымянный портал) и максимальное число результирующих строк (ноль означает «выбрать все строки»). Число результирующих строк имеет значение только для порталов, содержащих команды, возвращающие наборы строк; в других случаях команда всегда выполняется до завершения и число строк игнорируется. Возможные ответы на Execute такие же, как описанные выше для запросов, выполняющихся по протоколу простых запросов, за исключением того, что в ответ на Execute не выдаются ReadyForQuery или RowDescription.

Если Execute заканчивается до завершения выполнения портала (вследствие достижения ненулевого ограничения на число результирующих строк), сервер передаст сообщение PortalSuspended; появление этого сообщения говорит клиенту, что для завершения операции следует выдать еще одно сообщение Execute для того же портала. Сообщение CommandComplete, указывающее на завершение исходной команды SQL, не передается, пока не завершится выполнение портала. Таким образом, фаза Execute всегда заканчивается появлением одного из этих сообщений: CommandComplete, EmptyQueryResponse (если портал был создан из пустой строки запроса), ErrorResponse или PortalSuspended.

При завершении каждой цепочки сообщений расширенных запросов клиент должен выдать сообщение Sync. Это сообщение без параметров заставляет сервер закрыть текущую транзакцию, если она находится не внутри блока транзакции BEGIN/COMMIT («закрытие» обозначает фиксацию при отсутствии ошибок или откат при ошибке). Затем выдается ответ ReadyForQuery. Цель сообщения Sync — предоставить точку повторной синхронизации для восстановления в случае ошибки. Если ошибка выявляется при обработке любого сообщения расширенного запроса, сервер выдает ErrorResponse, затем считывает и отбрасывает сообщения вплоть до Sync, затем выдает ReadyForQuery и возвращается к обычной обработке сообщений. (Но обратите внимание, что он не будет пропускать сообщения, если ошибка выявляется во время обработки Sync — это гарантирует, что для каждого сообщения Sync будет передаваться ровно одно сообщение ReadyForQuery.)

Примечание
Сообщение Sync не приводит к закрытию блока транзакции, открытого командой BEGIN. Однако эту ситуацию можно выявить, поскольку в сообщении ReadyForQuery содержится информация о состоянии транзакции.

В дополнение к этим фундаментальным, необходимым операциям, в протоколе расширенного запроса можно выполнить несколько необязательных операций.

В сообщении Describe (в варианте для портала) задается имя существующего портала (или пустая строка для безымянного портала). Ответом является сообщение RowDescription, описывающее строки, которые будут возвращены при выполнении портала, или сообщение NoData, если портал не содержит запроса, который возвратит строки, или ErrorResponse, если такого портала нет.

В сообщении Describe (в варианте для оператора) задается имя существующего подготовленного оператора (или пустая строка для безымянного подготовленного оператора). Ответом является сообщение ParameterDescription, описывающее параметры, требующиеся для оператора, за которым следует сообщение RowDescription, описывающее строки, которые будут возвращены в результате выполнения оператора (или сообщение NoData, если оператор не возвратит строки). ErrorResponse выдается, если такого подготовленного оператора нет. Обратите внимание, что поскольку Bind еще не было выдано, сервер еще не знает, в каком формате будут возвращаться столбцы; в этом случае поля кодов форматов в сообщении RowDescription будут заполнены нулями.

Совет
В большинстве сценариев клиент должен выдать тот или иной вариант Describe, прежде чем выдавать Execute, чтобы наверняка знать, как интерпретировать результаты, которые он получит.

Сообщение Close закрывает существующий подготовленный оператор или портал и освобождает ресурсы. Выполнение Close для имени несуществующего оператора или портала ошибкой не является. Ответом на это сообщение обычно является CloseComplete, но может быть и ErrorResponse, если при освобождении ресурсов возникло какое-то затруднение. Обратите внимание, что закрытие подготовленного оператора неявно закрывает все открытые порталы, собранные из этого оператора.

Сообщение Flush не вызывает генерации каких-либо конкретных выходных данных, но указывает серверу передать все данных, находящиеся в очереди в его буферах вывода. Flush следует передавать после любой команды расширенного запроса, кроме Sync, если клиент желает проверить результаты этой команды, прежде чем выдавать следующие. Без Flush сообщения, возвращаемые сервером, будут объединяться в минимально возможное количество пакетов в целях максимального снижения сетевых издержек.

Примечание
Простое сообщение Query примерно равнозначно цепочке сообщений Parse, Bind, портального Describe, Execute, Close, Sync с использованием объектов подготовленного оператора и портала без имен и параметров. Одно различие заключается в том, что такое сообщение примет несколько операторов SQL в строке запроса, для каждого их них по очереди автоматически выполняя последовательность Bind/Describe/Execute. Другое различие заключается в том, что в ответ на него не передаются сообщения ParseComplete, BindComplete, CloseComplete или NoData.


Конвейерный режим

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

Один из способов этого добиться — объединить всю цепочку запросов в одну транзакцию, заключив ее в BEGIN ... COMMIT. Однако это не поможет, если нужно, чтобы какие-то команды фиксировались независимо от остальных.

Протокол расширенных запросов предоставляет и другой способ справиться с этой проблемой, исключив передачу сообщений Sync между зависимыми этапами. Так как после ошибки сервер будет пропускать командные сообщения, пока не найдет Sync, это позволяет в случае сбоя в предыдущей команде пропускать последующие команды в конвейере без необходимости клиенту явно управлять этим с помощью BEGIN и COMMIT. Независимо фиксируемые сегменты конвейера можно разделять сообщениями Sync.

Если клиент не выдает команду BEGIN явно, то каждое сообщение Sync обычно вызывает неявное выполнение COMMIT, если предыдущие этапы завершились успешно, или ROLLBACK в случае сбоя. Однако есть несколько команд DDL (например, CREATE DATABASE), которые нельзя выполнить в блоке транзакций. Если такая команда выполняется в конвейере, она выполнится успешно, только если будет в нем первой. Более того, в случае успеха она вызовет немедленную фиксацию для сохранения согласованности базы данных. Таким образом, сообщение Sync, отправленное сразу за такой командой, не вызовет ничего, кроме ответа ReadyForQuery.

При использовании этого метода завершение конвейера должно определяться подсчетом сообщений ReadyForQuery и ожиданием, когда их количество сравняется к количеством отправленных сообщений Syncs. Подсчет ответов о завершении команды ненадежен, поскольку некоторые команды могут пропускаться, и для них такие сообщения создаваться не будут.


Вызов функции

Подчиненный протокол «Вызов функции» позволяет клиенту запросить непосредственный вызов любой функции, существующей в системном каталоге pg_proc базы данных. При этом клиент должен иметь право на выполнение этой функции.

Примечание
Подчиненный протокол «Вызов функции» является устаревшей функциональностью, которую, вероятно, лучше не использовать в новом коде. Похожие результаты можно получить, установив подготовленный оператор с командой SELECT function($1, ...). Тогда цикл вызова функции можно заменить цепочкой Bind/Execute.

Цикл вызова функции запускается клиентом, передающим серверу сообщение FunctionCall. Затем сервер передает одно или несколько ответных сообщений, в зависимости от результатов вызова функции, и завершающее ответное сообщение ReadyForQuery. ReadyForQuery информирует клиента, что тот может безопасно передать новый запрос или вызов функции.

В этой фазе возможны следующие ответные сообщения от сервера:

ErrorResponse
Произошла ошибка.

FunctionCallResponse
Вызов функции был завершен и вернул результат, содержащийся в этом сообщении. (Обратите внимание, что протокол вызова функции может работать только с одним скалярным результатом, но не со строковым типом или набором результатов.)

ReadyForQuery
Обработка вызова функции завершена. ReadyForQuery будет передаваться всегда, независимо от того, закончилась ли обработка успешно или с ошибкой.

NoticeResponse
Было выдано предупреждающее сообщение, касающееся вызова функции. Замечания дополняют другие ответы, т. е. сервер продолжит обрабатывать команду.


Операции COPY

Команда COPY обеспечивает скоростную передачу массива данных на сервер или с сервера. Операции входящего и исходящего копирования переключают соединение в отдельные подчиненные протоколы, которые продолжают существовать до завершения операции.

Режим входящего копирования (данные передаются на сервер) запускается, когда сервер выполняет оператор SQL COPY FROM STDIN. Сервер передает клиенту сообщение CopyInResponse. Затем клиент должен передать ноль или более сообщений CopyData, формируя поток входных данных. (Границам сообщений необязательно совпадать с границами строк, хотя зачастую это разумное решение.) Клиент может завершить режим входящего копирования, передав либо сообщение CopyDone (приводящее к успешному завершению), либо сообщение CopyFail (которое приведет к сбою и ошибке в операторе SQL COPY). Затем сервер возвращается в режим обработки команд, в котором он находился перед началом выполнения COPY, т. е. в протокол простых или расширенных запросов. Затем он передаст либо CommandComplete (в случае успеха), либо ErrorResponse (в противном случае).

При возникновении ошибки (выявленной сервером) в режиме входящего копирования (включая получение сообщения CopyFail), сервер выдаст сообщение ErrorResponse. Если команда COPY была передана в сообщении расширенного запроса, сервер будет пропускать сообщения клиента, пока не получит сообщение Sync, после чего он выдаст ReadyForQuery и вернется в режим обычной обработки. Если команда COPY была передана в простом сообщении Query, остальная часть этого сообщения не учитывается и сразу выдается ReadyForQuery. В любом случае, любые последующие сообщения CopyData, CopyDone или CopyFail, выданные клиентом, будут просто удаляться.

В режиме входящего копирования сервер будет игнорировать полученные сообщения Flush и Sync. Получение сообщений любого другого типа, не относящегося к копированию, вызывает ошибку, которая прервет состояние входящего копирования, как описано выше. (Исключение для Flush и Sync введено для удобства клиентских библиотек, которые всегда посылают Flush или Sync после сообщения Execute, не проверяя, является ли выполняемая команда командой COPY FROM STDIN.)

Режим исходящего копирования (данные передаются с сервера) запускается, когда сервер выполняет оператор SQL COPY TO STDOUT. Сервер передает клиенту сообщение CopyOutResponse, за ним ноль или более сообщений CopyData (всегда по одному на строку), за ними CopyDone. Затем сервер возвращается в режим обработки команд, в котором он находился перед началом выполнения COPY, и передает CommandComplete. Клиент не может прервать передачу (кроме как закрыв соединение или выдав запрос Cancel), но он может не учитывать нежелательные сообщения CopyData и CopyDone.

При возникновении ошибки (выявленной сервером) в режиме исходящего копирования, сервер выдаст сообщение ErrorResponse и вернется в режим обычной обработки. Клиент должен воспринимать получение ErrorResponse как завершение режима исходящего копирования.

Сообщения CopyData могут перемежаться сообщениями NoticeResponse и ParameterStatus; клиенты должны их обрабатывать и быть готовыми получить и другие типы асинхронных сообщений (см. подраздел Асинхронные операции). В остальном, сообщения любых типов, кроме CopyData или CopyDone, могут восприниматься как завершающие режим исходящего копирования.

Имеется еще один режим, связанный с копированием, называемый двусторонним копированием, который обеспечивает скоростную передачу массива данных на сервер и с сервера. Режим двустороннего копирования запускается, когда сервер в режиме передачи WAL выполняет оператор START_REPLICATION. Сервер передает клиенту сообщение CopyBothResponse. Затем клиент и сервер могут обмениваться сообщениями CopyData, пока кто-то из них не передаст сообщение CopyDone. Когда сообщение CopyDone передает клиент, соединение переключается с режима двустороннего в режим исходящего копирования, и клиент больше не может передавать сообщения CopyData. Аналогично когда сообщение CopyDone передает сервер, соединение переключается с режима двустороннего в режим входящего копирования, и сервер больше не может передавать сообщения CopyData. Когда сообщение CopyDone передали обе стороны, режим копирования завершается, и сервер возвращается в режим обработки команд. При возникновении ошибки (выявленной сервером) в режиме двустороннего копирования, сервер выдаст сообщение ErrorResponse, будет пропускать сообщения клиента, пока не получит сообщение Sync, а затем выдаст ReadyForQuery и вернется в режим обычной обработки. Клиент должен воспринимать получение ErrorResponse как завершение двустороннего копирования; в этом случае CopyDone передаваться не должно. Более подробную информацию о подчиненном протоколе, распространяющемся на режим двустороннего копирования, см. в разделе Протокол потоковой репликации.

Сообщения CopyInResponse, CopyOutResponse и CopyBothResponse содержат поля, информирующие клиента о количестве столбцов в строке и коде формата для каждого столбца. (В текущей реализации для всех столбцов в заданной операции COPY используется один формат, но в конструкции сообщения это не предусмотрено.)


Асинхронные операции

Существует несколько сценариев, в которых сервер будет передавать сообщения, которые не были вызваны непосредственно потоком команд клиента. Клиенты должны быть готовы принять эти сообщения в любое время, даже когда они не занимаются запросом. Как минимум следует проверять такие сообщения, прежде чем начинать читать ответ на запрос.

Сообщения NoticeResponse могут генерироваться вследствие внешней активности; например, если администратор баз данных устраивает «быстрое» отключение базы данных, сервер передаст NoticeResponse, извещая об этом факте, прежде чем закроет соединение. Соответственно, клиенты должны быть всегда готовы принять и отобразить сообщения NoticeResponse, даже когда соединение условно простаивает.

Сообщения ParameterStatus будут генерироваться всякий раз, когда меняется активное значение любого параметра, о котором, по мнению сервера, должен знать клиент. Чаще всего это происходит в ответ на команду SQL SET, выполняемую клиентом, и в этом случае сообщение по сути синхронно — но статус параметра также может измениться, потому что администратор меняет файл конфигурации, а затем передает серверу сигнал SIGHUP. Кроме того, если команда SET откатывается, будет сгенерировано соответствующее сообщение ParameterStatus, содержащее текущее действительное значение.

В настоящее время есть жестко закодированный набор параметров, для которых будет генерироваться ParameterStatus: server_version, server_encoding, client_encoding, application_name, default_transaction_read_only, in_hot_standby, is_superuser, session_authorization, DateStyle, IntervalStyle, TimeZone, integer_datetimes и standard_conforming_strings. (default_transaction_read_only и in_hot_standby не отслеживались до версии 1.5.0.) Обратите внимание, что server_version, server_encoding и integer_datetimes являются псевдопараметрами, которые нельзя изменить после запуска сервера. В будущем этот набор может измениться или даже стать настраиваемым. Соответственно, клиент должен просто игнорировать ParameterStatus для параметров, которые ему неизвестны или не представляют интереса.

Если клиент выдает команду LISTEN, сервер будет передавать сообщение NotificationResponse (не путать с NoticeResponse!) всякий раз, когда для канала с тем же именем будет выполняться команда NOTIFY.

Примечание
В настоящее время сообщение NotificationResponse может быть передано только вне транзакции, и поэтому оно не может оказаться в середине цепочек ответов на команду, хотя может прийти сразу перед ReadyForQuery. Однако конструировать логику клиента, полагаясь на это, неразумно. Лучше, чтобы клиент был способен принимать NotificationResponse в любой момент протокола.


Отмена обрабатывающихся запросов

В ходе обработки запроса клиент может запросить отмену этого запроса. Запрос на отмену не передается серверу напрямую через открытое соединение из соображений эффективности реализации: мы не хотим, чтобы сервер постоянно проверял новые сообщения от клиента в процессе обработки запроса. Запросы на отмену должны быть относительно нечастыми, поэтому мы сделали их слегка громоздкими, чтобы избежать отрицательного влияния на обычную работу.

Чтобы выдать запрос на отмену, клиент открывает новое соединение с сервером и передает сообщение CancelRequest вместо сообщения StartupMessage, обычно передаваемого по новому соединению. Сервер обработает этот запрос, а затем закроет соединение. Из соображений безопасности на сообщение с запросом на отмену прямой ответ сервер не передает.

Сообщение CancelRequest будет игнорироваться, если только оно не содержит те же ключевые данные (PID и секретный ключ), что были переданы клиенту при запуске подключения. Если запрос соответствует PID и секретному ключу выполняющегося в данный момент внутреннего процесса, обработка текущего запроса прерывается. (В существующей реализации это производится путем передачи специального сигнала внутреннему процессу, обрабатывающему запрос.)

Сигнал отмены может подействовать, а может и не подействовать — к примеру, если он поступает после того, как сервер закончил обработку запроса, он не подействует. Если отмена срабатывает, она приводит к досрочному завершению текущей команды с сообщением об ошибке.

Результатом всего этого является то, что по соображениям безопасности и эффективности у клиента нет возможности напрямую определить, был ли запрос на отмену успешен. Он должен продолжать ожидать от сервера ответа на запрос. Выдача запроса на отмену просто увеличивает шансы, что текущий запрос завершится раньше и что он завершится не успехом, а сообщением об ошибке.

Поскольку запрос на отмену передается через новое соединение с сервером, а не через обычный канал связи клиент-сервер, такие запросы может выдавать любой процесс, а не только клиент, запрос которого нужно отменить. Это может обеспечить дополнительную гибкость при построении многопроцессных приложений. Также это приводит к угрозе безопасности, поскольку попытаться отменить запросы могут и неавторизованные пользователи. Для устранения этой угрозы безопасности требуется передавать в запросах на отмену динамически генерируемый секретный ключ.


Завершение

Обычная, постепенная процедура завершения состоит в том, что клиент передает сообщение Terminate и немедленно закрывает соединение. Получив это сообщение, серверный процесс закрывает соединение и завершается.

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

Другие сценарии завершения обусловлены различными случаями сбоя, например, при отказе дампа памяти на одной или другое стороне, при потере канала связи, при потере синхронизации по границам сообщений и т. п. Если клиент или сервер видит, что соединение неожиданно закрылось, он должен очистить ресурсы и завершиться. У клиента при этом есть вариант запустить новый серверный процесс, переподключившись к серверу, если он сам не хочет завершиться. Закрытие соединения также рекомендуется при получении сообщения нераспознаваемого типа, поскольку это может указывать на потерю синхронизации по границам сообщений.

При обычном или аварийном завершении сеанса все открытые транзакции откатываются, а не фиксируются. Однако следует обратить внимание, что если клиент отключается в процессе обработки запроса, отличного от SELECT, сервер, скорее всего, завершит запрос, прежде чем заметит отключение. Если запрос выполняется вне блока транзакции (последовательности BEGIN ... COMMIT), то его результаты могут быть зафиксированы до обнаружения отключения.


Шифрование сеанса с SSL

Если QHB была собрана с поддержкой SSL, взаимодействия клиента и сервера могут быть зашифрованы с использованием SSL. Это обеспечивает защиту связи в средах, где взломщики могут перехватить трафик сеанса. Более подробную информацию о шифровании сеансов QHB с помощью SSL см. в разделе Защита TCP/IP-соединений посредством SSL.

Чтобы запустить соединение с SSL-шифрованием, клиент сначала передает сообщение SSLRequest вместо StartupMessage. Затем сервер отвечает одним байтом, содержащим S или N, показывая, что он желает или не желает выполнять SSL-шифрование соответственно. На этом этапе клиент может закрыть соединение, если ответ его не удовлетворяет. Чтобы продолжить после получения S, он выполняет начальное согласование SSL с сервером (не описывается здесь, т. к. является частью спецификации SSL). Если это согласование проходит успешно, клиент продолжает соединение, передавая обычное сообщение StartupMessage. В этом случае StartupMessage и все последующие данные будут зашифрованы SSL. Чтобы продолжить после получения N, клиент передает обычное сообщение StartupMessage и продолжает взаимодействие с сервером без шифрования. (Как вариант, клиенту разрешается выдать сообщение GSSENCRequest после получения в ответ N, чтобы попытаться использовать вместо SSL шифрование GSSAPI.)

Когда SSL-шифрование может быть выполнено, ожидается, что сервер передаст только один байт с S, а затем будет ожидать от клиента начала согласования SSL. Если на этом этапе доступны для чтения дополнительные байты, это, скорее всего, означает, что «незаконный посредник» пытается выполнить атаку с переполнением буфера (CVE-2021-23222). Клиенты должны кодироваться так, чтобы либо прочитать из сокета ровно один байт, прежде чем передать этот сокет своей библиотеке SSL, либо, если ими были прочитаны дополнительные байты, считать это нарушением протокола.

Начальное сообщение SSLRequest также может передаваться в соединении, которое открывается для передачи сообщения CancelRequest.

Хотя сам протокол не предоставляет способа заставить сервер включить шифрование SSL, администратор может сконфигурировать сервер так, чтобы в качестве дополнения при проверке подлинности клиента тот не принимал незашифрованные сеансы.


Шифрование сеанса с GSSAPI

Если QHB была собрана с поддержкой GSSAPI, взаимодействия клиента и сервера могут быть зашифрованы с использованием GSSAPI. Это обеспечивает защиту связи в средах, где взломщики могут перехватить трафик сеанса. Более подробную информацию о шифровании сеансов QHB с помощью GSSAPI см. в разделе Защита TCP/IP-соединений посредством шифрования GSSAPI.

Чтобы запустить соединение с GSSAPI-шифрованием, клиент сначала передает сообщение GSSENCRequest вместо StartupMessage. Затем сервер отвечает одним байтом, содержащим G или N, показывая, что он желает или не желает выполнять GSSAPI-шифрование соответственно. На этом этапе клиент может закрыть соединение, если ответ его не удовлетворяет. Чтобы продолжить после получения G, используя привязки GSSAPI на языке C в соответствии со стандартом RFC 2744 или равнозначным, выполните запуск GSSAPI, вызывая функцию gss_init_sec_context() в цикле и передавая результат серверу, сначала без входных данных, а затем с каждым результатом от сервера, пока функция не перестанет выдавать выходные данные. При передаче результатов gss_init_sec_context() серверу перед сообщением добавьте его длину в виде четырехбайтового целого в сетевом порядке байтов. Чтобы продолжить после получения N, клиент передает обычное сообщение StartupMessage и продолжает взаимодействие с сервером без шифрования. (Как вариант, клиенту разрешается выдать сообщение SSLRequest после получения в ответ N, чтобы попытаться использовать вместо GSSAPI шифрование SSL.)

Кроме того, клиент должен быть готов обработать сообщение ErrorMessage, полученное от сервера в ответ на GSSENCRequest. Это может произойти, только если версия сервера не поддерживает шифрование GSSAPI в QHB. В этом случае соединение должно быть закрыто, но клиент может предпочесть открыть новое соединение и продолжить взаимодействие с сервером, не запрашивая GSSAPI-шифрование.

Когда шифрование GSSAPI может быть выполнено, ожидается, что сервер передаст только один байт с G, а затем будет ожидать от клиента начала согласования GSSAPI. Если на этом этапе доступны для чтения дополнительные байты, это, скорее всего, означает, что «незаконный посредник» пытается выполнить атаку с переполнением буфера (CVE-2021-23222). Клиенты должны кодироваться так, чтобы либо прочитать из сокета ровно один байт, прежде чем передать этот сокет своей библиотеке GSSAPI, либо, если ими были прочитаны дополнительные байты, считать это нарушением протокола.

Начальное сообщение GSSENCRequest также может передаваться в соединении, которое открывается для передачи сообщения CancelRequest.

Если GSSAPI-шифрование было успешно установлено, используйте функцию gss_wrap() для шифрования обычного сообщения StartupMessage и всех последующих данных, добавляя длину результата gss_wrap() в виде четырехбайтового целого в сетевом порядке байтов перед собственно зашифрованными полезными данными. Обратите внимание, что сервер примет от клиента зашифрованные пакеты, только если они меньше 16 КБ; чтобы определить размер незашифрованного сообщения, которое уложится в это ограничение, клиент должен использовать функцию gss_wrap_size_limit(), а более крупные сообщения следует разбивать на части с помощью нескольких вызовов функции gss_wrap(). Обычно сегменты незашифрованных данных имеют размер 8 КБ, поэтому зашифрованные пакеты получаются размером чуть больше 8 КБ, но вполне умещаются в лимит 16 КБ. Можно ожидать, что сервер тоже не будет передавать клиенту зашифрованные пакеты размером больше 16 КБ.

Хотя сам протокол не предоставляет способа заставить сервер включить шифрование GSSAPI, администратор может сконфигурировать сервер так, чтобы в качестве дополнения при проверке подлинности клиента тот не принимал незашифрованные сеансы.



Аутентификация SASL

SASL — это инфраструктура для аутентификации в протоколах, ориентированных на соединения. В настоящее время QHB реализует два механизма аутентификации SASL: SCRAM-SHA-256 и SCRAM-SHA-256-PLUS. В будущем могут быть добавлены и другие. Перечисленные ниже этапы показывают, как в целом осуществляется аутентификация SASL, а в следующем подразделе более подробно рассматриваются SCRAM-SHA-256 и SCRAM-SHA-256-PLUS.

Поток сообщений аутентификации SASL

  1. Чтобы начать обмен аутентификационной информацией SASL, сервер передает сообщение AuthenticationSASL. Оно содержит список механизмов аутентификации SASL, которые сервер может принять, в порядке предпочтений сервера.

  2. Клиент выбирает один из поддерживаемых механизмов из списка и передает серверу сообщение SASLInitialResponse. Это сообщение содержит имя выбранного механизма и, возможно, «Начальный ответ клиента», если это использует выбранный механизм.

  3. За этим последует одно или несколько сообщений вызова от сервера и ответа от клиента. Каждый вызов от сервера передается в сообщении AuthenticationSASLContinue, за которым следует ответ от клиента в сообщении SASLResponse. Конкретные детали этих сообщений зависят от механизма.

  4. Наконец, когда обмен аутентификационной информацией успешно завершается, сервер передает сообщение AuthenticationSASLFinal и сразу за ним сообщение AuthenticationOk. AuthenticationSASLFinal включает дополнительные данные от сервера клиенту, содержимое которых определяется выбранным механизмом аутентификации. Если механизм аутентификации не использует дополнительные данные, посылаемые в завершение обмена, сообщение AuthenticationSASLFinal не передается.

В случае ошибки сервер может прервать аутентификацию на любом этапе и передать ErrorMessage.


Аутентификация SCRAM-SHA-256

На сегодняшний день реализованы следующие механизмы SASL: SCRAM-SHA-256 и его вариант со связыванием каналов SCRAM-SHA-256-PLUS. Они подробно описываются в RFC 7677 и RFC 5802.

Когда в QHB применяется SCRAM-SHA-256, сервер игнорирует имя пользователя, которое клиент передает в client-first-message. Вместо этого используется имя пользователя, переданное ранее в сообщении о запуске. Хотя SCRAM требует, чтобы для имени пользователя применялась кодировка UTF-8, QHB поддерживает несколько кодировок символов, поэтому представить имя пользователя QHB в UTF-8 не всегда возможно.

Спецификация SCRAM требует, чтобы пароль тоже был в UTF-8 и обрабатывался алгоритмом SASLprep. Однако QHB не требует, чтобы для пароля применялась кодировка UTF-8. Когда устанавливается пароль пользователя, он обрабатывается SASLprep так, как если бы был в UTF-8, независимо от фактической кодировки. Однако если он представлен недопустимой для UTF-8 последовательностью байтов или содержит последовательности байтов UTF-8, которые запрещены алгоритмом SASLprep, ошибка выдана не будет, а будет использован исходный пароль, без обработки SASLprep. Это позволяет нормализовать пароль, если он в UTF-8, но при этом использовать и пароль не в UTF-8, и не требует, чтобы система знала, в какой кодировке задан пароль.

Связывание каналов поддерживается в QHB, собранной с поддержкой SSL. Имя механизма SASL для SCRAM со связыванием каналов — SCRAM-SHA-256-PLUS. QHB использует тип связывания каналов tls-server-end-point.

В SCRAM без связывания каналов сервер выбирает случайное число, которое передается клиенту для смешивания с предоставленным пользователем паролем в передаваемом в ответ хэше пароля. Хотя это препятствует успешной повторной передаче хэша пароля в последующем сеансе, но не помешает поддельному серверу между настоящим сервером и клиентом передать случайное число сервера и успешно пройти аутентификацию.

SCRAM со связыванием каналов предотвращает такие атаки через посредника путем подмешивания подписи сертификата сервера в передаваемый хэш пароля. Хотя поддельный сервер может повторить передачу сертификата настоящего сервера, у него нет доступа к закрытому ключу, соответствующему этому сертификату, и поэтому он не сможет доказать, что является его владельцем, что приведет к сбою при установке SSL-соединения.

Пример

  1. Сервер передает сообщение AuthenticationSASL. Оно содержит список механизмов аутентификации SASL, которые сервер может принять. Если сервер собран с поддержкой SSL, сюда войдут SCRAM-SHA-256-PLUS и SCRAM-SHA-256, иначе — только последний.

  2. Клиент отвечает, передавая сообщение SASLInitialResponse, где указан выбранный механизм, SCRAM-SHA-256 или SCRAM-SHA-256-PLUS. (Клиент волен выбрать любой механизм, но для большей безопасность ему следует выбирать вариант со связыванием каналов, если он это поддерживает.) В поле «Начальный ответ клиента» это сообщение содержит данные client-first-message SCRAM. client-first-message также содержит выбранный клиентом тип связывания каналов.

  3. Сервер передает сообщение AuthenticationSASLContinue, содержащее данные SCRAM server-first-message.

  4. Клиент передает сообщение SASLResponse, содержащее данные SCRAM client-final-message.

  5. Сервер передает сообщение AuthenticationSASLFinal, содержащее данные SCRAM server-final-message, и сразу за ним сообщение AuthenticationOk.



Протокол потоковой репликации

Чтобы инициировать потоковую репликацию, клиент передает в сообщении о запуске параметр replication. Логическое значение true (или on, yes, 1) указывает серверу перейти в режим передатчика WAL физической репликации, в котором вместо операторов SQL может выдаваться лишь небольшой набор команд репликации, показанный ниже.

Передача в параметре replication значения database указывает серверу перейти в режим передатчика WAL логической репликации и подключиться к базе данных, указанной в параметре dbname. В режиме передачи WAL логической репликации могут выдаваться как показанные ниже команды репликации, так и обычные команды SQL.

В режиме передачи WAL как физической, так и логической репликации может применяться только протокол простых запросов.

В целях тестирования команд репликации можно установить соединение для репликации посредством psql или любого другого инструмента на базе libpq со строкой подключения, включающей параметр replication, например:

psql "dbname=qhb replication=database" -c "IDENTIFY_SYSTEM;"

Однако зачастую полезнее использовать qhb_receivewal (для физической репликации) или qhb_recvlogical (для логической репликации).

Команды репликации протоколируются в журнал сервера, когда включен параметр log_replication_commands.

В режиме репликации принимаются следующие команды:

IDENTIFY_SYSTEM

Запрашивает идентификационные данные сервера. Сервер отвечает результирующим набором с одной строкой, содержащей четыре поля:

systemid (text)
Уникальный идентификатор системы, идентифицирующий кластер. С его помощью можно убедиться в том, что базовая резервная копия, используемая для инициализации резервного сервера, получена из того же кластера.

timeline (int4)
Идентификатор текущей временной шкалы. Также полезен для проверки того, что резервный сервер согласован с основным.

xlogpos (text)
Текущая позиция сброшенных на диск данных WAL. Позволяет узнать позицию в журнале упреждающей записи, с которой может начаться потоковая передача.

dbname (text)
База данных, к которой произведено подключение, или NULL.
.

SHOW имя

Запрашивает у сервера текущее значение параметра времени выполнения. Эта команда схожа с командой SQL SHOW.

имя
Имя параметра времени выполнения. Доступные параметры описываются в главе Конфигурация сервера.
.

TIMELINE_HISTORY врем_шкала

Запрашивает у сервера файл истории для временной шкалы врем_шкала. Сервер отвечает результирующим набором из одной строки, содержащей два поля. Хотя эти поля и помечены как имеющие тип text, по сути они возвращают исходные байты без преобразования кодировки:

filename (text)
Имя файла с историей временной шкалы, например 00000002.history.

content (text)
Содержимое файла с историей временной шкалы.
.

CREATE_REPLICATION_SLOT имя_слота [ TEMPORARY ] { PHYSICAL [ RESERVE_WAL ] | LOGICAL плагин_вывода [ EXPORT_SNAPSHOT | NOEXPORT_SNAPSHOT | USE_SNAPSHOT ] }

Создает слот физической или логической репликации. Подробную информацию о слотах репликации см. в подразделе Слоты репликации.

имя_слота
Имя создаваемого слота. Задаваемое имя должно быть допустимым для слота репликации (см. подраздел Запросы и манипуляции слотов репликации).

плагин_вывода
Имя плагина вывода, используемого для логического декодирования (см. раздел Плагины вывода логического декодирования).

TEMPORARY
Указывает, что этот слот репликации является временным. Временные слоты не сохраняются на диске и автоматически удаляются при ошибке или завершении сеанса.

RESERVE_WAL
Указывает, что это слот физической репликации резервирует WAL немедленно. Без этого указания WAL резервируется только при подключении клиента потоковой репликации.

EXPORT_SNAPSHOT
NOEXPORT_SNAPSHOT
USE_SNAPSHOT
Решает, что делать со снимком состояния, создаваемым во время инициализации логического слота. С EXPORT_SNAPSHOT (по умолчанию), этот снимок будет экспортироваться для использования в других сеансах. Этот параметр нельзя применять внутри транзакции. С USE_SNAPSHOT снимок будет использоваться для текущей транзакции, выполняющей команду. Этот параметр следует применять в транзакции, и команда CREATE_REPLICATION_SLOT должна быть в этой транзакции первой. Наконец, с NOEXPORT_SNAPSHOT снимок будет использоваться для логического декодирования в обычном режиме, но ничего больше с ним делать нельзя.

В ответ на эту команду сервер передаст результирующий набор из одной строки, содержащей следующие поля:

slot_name (text)
Имя только что созданного слота репликации.

consistent_point (text) Позиция в WAL, в которой слот стал согласованным. Это самая ранняя позиция, с которой через этот слот репликации может начаться потоковая передача.

snapshot_name (text)
Идентификатор снимка, экспортированного этой командой. Этот снимок действителен, пока через это соединение не будет выполнена новая команда или это соединение репликации не будет закрыто. NULL, если созданный слот является физическим.

output_plugin (text)
Имя плагина вывода, используемого только что созданным слотом репликации. NULL, если созданный слот является физическим.
.

START_REPLICATION [ SLOT имя_слота ] [ PHYSICAL ] XXX/XXX [ TIMELINE врем_шкала ]

Указывает серверу начать потоковую передачу WAL, начиная с позиции XXX/XXX. Если задан параметр TIMELINE, потоковая передача начинается на временной шкалы врем_шкала, иначе выбирается текущая временная шкала сервера. Сервер может ответить ошибкой, например, если запрошенный фрагмент WAL уже был утилизирован. В случае успеха сервер отвечает сообщением CopyBothResponse, а затем начинает передавать клиенту поток WAL.

Если в параметрах передается имя_слота, через него будет отслеживаться состояние репликации, чтобы сервер знал, какие сегменты WAL (а если включен режим hot_standby_feedback, то и в каких транзакциях) все еще нужны этому резервному серверу.

Если клиент запрашивает не последнюю, но имеющуюся в истории сервера линию времени, сервер будет передавать все записи WAL на этой временной шкале, начиная с запрошенной стартовой точки до момента, когда сервер переключился на другую линию времени. Если клиент запрашивает потоковую передачу со стартовой точкой ровно в конце старой временной шкалы, сервер полностью пропускает режим COPY.

После передачи всех записей WAL на временной шкале, не являющейся последней, сервер завершит потоковую передачу, выйдя из режима COPY. Когда клиент подтверждает это, тоже выходя из режима COPY, сервер передает результирующий набор с одной строкой и двумя столбцами, указывая на следующую временную шкалу в истории этого сервера. В первом столбце содержится идентификатор следующей временной шкалы (тип int8), а во втором — позиция в WAL, где произошло переключение (тип text). Обычно позиция переключения совпадает с позицией завершения передачи WAL, но бывают исключения, когда сервер может передавать некоторые записи WAL из старой линии времени, которые он сам еще не воспроизвел до переключения. Наконец сервер передает два сообщения CommandComplete (одно завершает CopyData, а другое завершает саму команду START_REPLICATION), после чего готов принять новую команду.

Данные WAL передаются в виде серии сообщений CopyData. (Это позволяет перемежать их с другой информацией; в частности, сервер может передать сообщение ErrorResponse, если сталкивается со сбоем после начала потоковой передачи.) Полезные сведения каждого сообщения CopyData от сервера к клиенту содержат данные в одном из следующих форматов:

XLogData (B)

Byte1('w')
Указывает, что в сообщении содержатся данные WAL.

Int64
Стартовая точка данных WAL в этом сообщении.

Int64
Текущая позиция конца WAL на сервере.

Int64
Показания системных часов сервера в момент передачи, в микросекундах с полуночи 2000-01-01.

Byten
Фрагмент потока данных WAL.
Одна запись WAL никогда не разделяется на два сообщения XLogData. Когда запись WAL пересекает границу страницы WAL и тем самым уже разделяет используемую продолжающуюся запись, ее можно разделить по границе страницы. Другими словами, первая основная запись WAL и продолжающие ее записи могут передаваться в разных сообщениях XLogData.

Primary keepalive message (B)

Byte1('k')
Показывает, что это сообщение проверки активности отправителя.

Int64
Текущая позиция конца WAL на сервере.

Int64
Показания системных часов сервера в момент передачи, в микросекундах с полуночи 2000-01-01.

Byte1
Значение 1 означает, что клиент должен ответить на это сообщение как можно скорее во избежание отключения по истечении времени ожидания. В противном случае ставится 0.

Принимающий процесс может передавать ответы отправителю в любое время, используя один из следующих форматов сообщений (также в полезных данных сообщения CopyData):

Standby status update (F)

Byte1('r')
Показывает, что это сообщение информирует о состоянии получателя.

Int64
Положение байта, следующего за последним байтом WAL, полученным и записанным на диск на резервном сервере.

Int64
Положение байта, следующего за последним байтом WAL, сброшенным на диск на резервном сервере.

Int64
Положение байта, следующего за последним байтом WAL, примененным на резервном сервере.

Int64
Показания системных часов клиента в момент передачи, в микросекундах с полуночи 2000-01-01.

Byte1
При значении 1 клиент запрашивает от сервера немедленный ответ на это сообщение. Это можно использовать для «прозванивания» сервера, чтобы проверить работоспособность соединения.

Hot Standby feedback message (F)

Byte1('h')
Показывает, что это сообщение обратной связи горячего резерва.

Int64
Показания системных часов клиента в момент передачи, в микросекундах с полуночи 2000-01-01.

Int32
Текущее глобальное значение xmin этого резервного сервера без учета catalog_xmin всех слотов репликации. Если это значение и следующее catalog_xmin равны 0, это воспринимается как уведомление о том, что через это соединение больше не будут передаваться сообщения обратной связи горячего резерва. Последующие ненулевые сообщения могут повторно запустить механизм обратной связи.

Int32
Эпоха глобального идентификатора транзакции xmin на резервном сервере.

Int32
Наименьшее значение catalog_xmin для всех слотов репликации на резервном сервере. Устанавливается в 0, если на резервном сервере не существует catalog_xmin или отключена обратная связь горячего резерва.

Int32
Эпоха идентификатора транзакции catalog_xmin на резервном сервере.
.

START_REPLICATION SLOT имя_слота LOGICAL XXX/XXX [ ( имя_параметра [ значение_параметра ] [, ...] ) ]

Указывает серверу начать потоковую передачу WAL для логической репликации, начиная с позиции XXX/XXX в WAL. Сервер может ответить ошибкой, например, если запрошенный фрагмент WAL уже был утилизирован. В случае успеха сервер отвечает сообщением CopyBothResponse, а затем начинает передавать клиенту поток WAL.

Сообщения внутри сообщения CopyBothResponse имеют тот же формат, что описан для команды START_REPLICATION ... PHYSICAL, включая два сообщения CommandComplete.

Для обработки выходных данных потоковой передачи используется плагин вывода, связанный с выбранным слотом.

SLOT имя_слота
Имя слота, из которого передаются изменения. Это обязательный параметр, который должен соответствовать существующему слоту логической репликации, созданному командой CREATE_REPLICATION_SLOT в режиме LOGICAL.

XXX/XXX
Позиция в WAL, с которой должна начаться потоковая передача.

имя_параметра
Имя параметра, передаваемого плагину логического декодирования этого слота.

значение_параметра
Необязательное значение в форме строковой константы, связанное с указанным параметром.
.

DROP_REPLICATION_SLOT имя_слота [ WAIT ]

Удаляет слот репликации, освобождая все зарезервированные ресурсы на стороне сервера. Если это логический слот, созданный не в той базе данных, к которой подключен передатчик WAL, данная команда завершается ошибкой.

имя_слота
Имя удаляемого слота.

WAIT
Этот параметр заставляет команду ждать, если слот активен, пока он не станет неактивным, вместо того чтобы выдать ошибку (поведение по умолчанию).
.

BASE_BACKUP [ LABEL 'метка' ] [ PROGRESS ] [ FAST ] [ WAL ] [ NOWAIT ] [ MAX_RATE скорость ] [ TABLESPACE_MAP ] [ NOVERIFY_CHECKSUMS ] [ MANIFEST параметр_манифеста ] [ MANIFEST_CHECKSUMS алгоритм_контрольной_суммы ]

Указывает серверу начать потоковую передачу базовой резервной копии. Система автоматически перейдет в режим резервного копирования до начала передачи и выходит из него, когда копирование завершается. Принимаются следующие параметры:

LABEL 'метка'
Устанавливает метку для резервной копии. Если метка не задана, будет установлена метка base backup. Правила применения кавычек для метки такие же, как для стандартной строки SQL при включенном параметре standard_conforming_strings.

PROGRESS
Запросить информацию, необходимую для генерирования отчета о ходе выполнения передачи. В ответ сервер передаст примерный размер в заголовке каждого табличного пространства, из которого можно вычислить, насколько продвинулась передача потока. Вычисление проводится путем подсчета размеров всех файлов еще до начала передачи и поэтому может отрицательно сказаться на производительности. В частности, может возрасти задержка передачи первых данных. Поскольку файлы базы данных в ходе резервного копирования могут меняться, оценка размера будет только приблизительной, и он может как увеличиться, так и уменьшиться за время между вычислением и передачей реальных файлов.

FAST
Запросить быструю контрольную точку.

WAL
Включить в резервную копию необходимые сегменты WAL. Сюда войдут все файлы, которые с начала и до конца копирования находились в подкаталоге pg_wal tar-файла базового каталога.

NOWAIT
По умолчанию резервное копирование будет ждать завершения архивации последнего необходимого сегмента WAL или выдавать предупреждение, если архивация журнала не включена. Указание NOWAIT выключает и ожидание, и предупреждение, поэтому за обеспечение наличия необходимого журнала будет отвечать клиент.

MAX_RATE скорость
Ограничить (дросселировать) максимальный объем данных, передаваемый от сервера клиенту за единицу времени. Ожидаемая единица измерения — килобайты в секунду. Если этот параметр задается, его значение должно быть либо равно нулю, либо находиться в диапазоне от 32 КБ до 1 ГБ (включая границы). Если передается ноль или параметр не задан, скорость передачи не ограничивается.

TABLESPACEMAP
Включить информацию о символических ссылках, находящихся в каталоге pg_tblspc, в файл tablespace_map. Файл карты табличных пространств содержит имена всех символических ссылок, существующих в каталоге pg_tblspc/ и полный путь для каждой ссылки.

NOVERIFY_CHECKSUMS
По умолчанию контрольные суммы проверяются во время базового резервного копирования, если они включены. Указание NOVERIFY_CHECKSUMS выключает эту проверку.

MANIFEST параметр_манифеста
Когда этот параметр задается со значением yes или force-encode, вместе с резервной копией создается и передается манифест копии. Этот манифест представляет собой список всех файлов, находящихся в копии, за исключением файлов WAL, которые могут быть включены дополнительно. Также здесь сохраняется размер, время последнего изменения и, возможно, контрольная сумма каждого файла. Со значением force-encode все имена файлов кодируются в шестнадцатеричном формате; в противном случае этот тип кодирования применяется только к файлам, имена которых представлены не байтовыми последовательностями UTF8. Значение force-encode предназначено в первую очередь для тестирования, чтобы убедиться, что клиенты, которые читают манифест копии, могут прочитать закодированные имена корректно. Для совместимости с предыдущими версиями по умолчанию установлено MANIFEST 'no'.

MANIFEST_CHECKSUMS алгоритм_контрольной_суммы
Указывает алгоритм контрольной суммы, который должен применяться к каждому файлу, включенному в манифест копии. В настоящее время доступны алгоритмы: NONE (отсутствует), CRC32C, SHA224, SHA256, SHA384 и SHA512. Значение по умолчанию — CRC32C.

Когда начинается резервное копирование, сервер сначала передает два обычных результирующих набора, а за ними — один или несколько результатов CopyOutResponse.

Первый обычный результирующий набор содержит начальную позицию резервной копии, в одной строке с двумя столбцами. Первый столбец содержит начальную позицию в формате XLogRecPtr, а второй — идентификатор соответствующей временной шкалы.

Во втором обычном результирующем наборе содержится по одной строке для каждого табличного пространства. Эта строка содержит следующие поля:

spcoid (oid)
OID табличного пространства или NULL, если это базовый каталог.

spclocation (text)
Полный путь к каталогу табличного пространства или NULL, если это базовый каталог.

size (int8)
Примерный размер табличного пространства в килобайтах (1024 байта), если был запрошен отчет о ходе выполнения передачи; в противном случае NULL.

После второго обычного результирующего набора будет передан один или несколько результатов CopyOutResponse: один для основного каталога данных и по одному для каждого дополнительного табличного пространства, отличного от pg_default и pg_global. Данные в результатах CopyOutResponse будут представлять собой дамп в формате tar (исходя из «формата обмена ustar», указанного в стандарте POSIX 1003.1-2008) содержимого табличных пространств, за исключением того, что будут опущены два замыкающих блока нулей, указанных в стандарте. После завершения передачи данных tar, и если был запрошен манифест копии, передается еще один результат CopyOutResponse, содержащий данные манифеста для текущей базовой резервной копии. Затем в любом случае будет передан заключительный обычный результирующий набор, содержащий конечную позицию копии в WAL в том же формате, что и начальная позиция.

Архив tar каталога данных и всех табличных пространств будет содержать все файлы в этих каталогах, независимо от того, являются ли они файлами QHB или другими файлами, добавленными в тот же каталог. Исключаются только следующие файлы:

  • qhbmaster.pid

  • qhbmaster.opts

  • pg_internal.init (находится в нескольких каталогах)

  • Различные временные файлы и каталоги, созданные во время работы сервера QHB, например, файлы или каталоги с именем, начинающимся на pgsql_tmp, и временные отношения.

  • Нежурналируемые отношения, за исключением ветви инициализации, которая необходима для пересоздания нежурналируемого отношения (пустого) при восстановлении.

  • pg_wal, включая подкаталоги. Если резервное копирование запускается с включением файлов WAL, в архив войдет сгенерированная версия pg_wal, но она будет содержать только файлы, необходимые для работы копии, но не все остальное содержимое этого каталога.

  • pg_dynshmem, pg_notify, pg_replslot, pg_serial, pg_snapshots, pg_stat_tmp и pg_subtrans копируются как пустые каталоги, даже если они являются символическими ссылками).

  • Файлы, отличные от обычных файлов и каталогов, например, символические ссылки (кроме перечисленных выше каталогов) и файлы специальных устройств, пропускаются. (Символические ссылки в pg_tblspc сохраняются.)

Если файловая система сервера это поддерживает, в архив добавляются сведения о владельце, группе и режиме файла.



Протокол логической потоковой репликации

В этом разделе описывается протокол логической репликации, представляющий собой поток сообщений, который запускается командой репликации START_REPLICATION SLOT имя_слота LOGICAL.

Протокол логической потоковой репликации собирается на примитивах протокола физической потоковой репликации.


Параметры логической потоковой репликации

Команда логической репликации START_REPLICATION принимает следующие параметры:

proto_version
Версия протокола. В настоящее время поддерживаются версии 1 и 2. Версия 2 поддерживается только для серверов версии 1.5.0 и выше и допускает потоковую репликацию больших выполняющихся транзакций.

publication_names
Список разделенных запятыми имен публикаций, на которые следует подписаться (получать их изменения). Имена отдельных публикаций воспринимаются как стандартные имена объектов и при необходимости тоже могут заключаться в кавычки.


Сообщения протокола логической репликации

Отдельные сообщения протокола рассматриваются в следующих подразделах. Сами сообщения описаны в разделе Форматы сообщений логической репликации.

Все сообщения верхнего уровня начинаются с байта, указывающего на тип сообщения. Хотя он представлен в коде в виде символа, это знаковый байт, с которым не связана никакая кодировка.

Поскольку протокол потоковой репликации предоставляет длину сообщения, нет необходимости указывать длину в заголовках сообщений верхнего уровня.


Поток сообщений протокола логической репликации

За исключением команды START_REPLICATION и сообщений о ходе воспроизведения, весь информационный поток передается от сервера клиенту.

Протокол логической репликации передает отдельные транзакции одну за другой. Это означает, что все сообщения между парой сообщений Begin и Commit принадлежат одной транзакции. Также протокол передает изменения из больших выполняющихся транзакций между парой сообщений Stream Start и Stream Stop. Последний поток для такой транзакции содержит сообщение Stream Commit или Stream Abort.

Каждая передаваемая транзакция содержит ноль или более сообщений DML (Insert, Update, Delete). В случае каскадной схемы она может также содержать сообщения Origin. Это сообщение показывает, что транзакция была запущена в другом узле репликации. Поскольку узел репликации в контексте протокола логической репликации может быть чем угодно, единственным идентификатором является его имя. За верное восприятие этого имени (если это необходимо) отвечают нижестоящие узлы. Сообщение Origin всегда передается перед всеми сообщениями DML в транзакции.

Каждое сообщение DML содержит OID отношения, идентифицирующий целевое отношение на стороне публикации. Перед первым сообщением DML для данного OID отношения будет передано сообщение Relation, описывающее схему этого отношения. Впоследствии, если определение отношения изменится после отправки последнего сообщения Relation, будет передано новое сообщение Relation. (Протокол предполагает, что клиент способен сохранить в памяти эти метаданные для любого требуемого количества отношений.)

В сообщениях Relation типы столбцов определяются их OID. В случае встроенного типа предполагается, что клиент может найти этот OID типа локально, поэтому никакие дополнительные данные не нужны. Для OID не встроенного типа перед сообщением Relation будет передано сообщение Type, предоставляющее имя типа, связанное с этим OID. Таким образом, клиент, которому нужно однозначно идентифицировать типы столбцов в отношении, должен кэшировать содержимое сообщений Type, и сначала проверять этот кэш на предмет того, не определен ли там OID типа, а если нет, искать OID типа локально.



Типы данных сообщений

В этом разделе описываются базовые типы данных, используемые в сообщениях.

Intn(i)

n-битовое целое число с сетевым порядком байтов (первым идет наиболее значащий байт). Если указано i, здесь будет фигурировать именно указанное значение, в противном случае значение переменное. Например: Int16, Int32(42).

Intn[k]

Массив из k n-битовых целых чисел, все с сетевым порядком байтов. Длина массива k всегда определяется из предыдущего поля сообщения. Например: Int16[M].

String(s)

Строка с завершающим нулем (строка в стиле C). Ограничение на длину строк отсутствует. Если указано s, здесь будет фигурировать именно указанное значение, в противном случае значение переменное. Например: String, String("user").

Примечание
Нет никакого предопределенного ограничения длины строки, которую может возвратить сервер. При кодировании клиента рекомендуется использовать расширяемый буфер, чтобы он мог принять все, что умещается в памяти. Если это невыполнимо, прочитайте строку целиком и отбросьте завершающие символы, которые помещаются в ваш буфер фиксированного размера.

Byten(c)

Ровно n байт. Если ширина поля n не является константой, она всегда определяется из предыдущего поля сообщения. Если указано c, оно задает точное значение. Например: Byte2, Byte1('\n').



Форматы сообщений

В этом разделе подробно описывается формат каждого сообщения. Все сообщения помечены символами, показывающими, что их может передавать клиент (F), сервер (B) или они оба (F & B). Обратите внимание, что хотя каждое сообщение включает счетчик байтов в начале, формат сообщения определен так, чтобы конец сообщения можно было найти, не обращаясь к счетчику байтов. Это помогает при проверке допустимости. (Исключением является сообщение CopyData, поскольку оно образует часть потока данных; содержимое любого отдельного сообщения CopyData нельзя интерпретировать само по себе.)

AuthenticationOk (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(0)
Указывает, что аутентификация была успешной.

AuthenticationKerberosV5 (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(2)
Указывает, что требуется аутентификация по Kerberos V5.

AuthenticationCleartextPassword (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(3)
Указывает, что требуется пароль в виде открытого текста.

AuthenticationMD5Password (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(12)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(5)
Указывает, что требуется пароль, зашифрованный хэшем MD5.

Byte4
Соль, используемая при шифровании пароля.

AuthenticationSCMCredential (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(6)
Указывает, что требуется сообщение с учетными данными SCM.

AuthenticationGSS (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(7)
Указывает, что требуется аутентификация GSSAPI.

AuthenticationGSSContinue (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(8)
Указывает, что это сообщение содержит данные GSSAPI.

Byten
Данные аутентификации GSSAPI.

AuthenticationSASL (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(10)
Указывает, что требуется аутентификация SASL.

Тело сообщения представляет собой список механизмов аутентификации SASL в порядке предпочтений сервера. После последнего имени механизма аутентификации требуется завершающий нулевой байт. Для каждого механизма передается следующее:

String
Имя механизма аутентификации SASL.

AuthenticationSASLContinue (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(11)
Указывает, что это сообщение содержит вызов SASL.

Byten
Данные SASL, специфичные для используемого механизма SASL.

AuthenticationSASLFinal (B)

Byte1('R')
Показывает, что это сообщение является запросом аутентификации.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(12)
Указывает, что аутентификация SASL завершилась.

Byten
Выходные «дополнительные данные» SASL, специфичные для используемого механизма SASL.

BackendKeyData (B)

Byte1('K')
Показывает, что сообщение содержит ключевые данные для отмены запросов. Клиент должен сохранить эти значения, если желает иметь возможность впоследствии выдавать сообщения CancelRequest.

Int32(12)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32
PID этого серверного процесса.

Int32
Секретный ключ этого серверного процесса.

Bind (F)

Byte1('B')
Показывает, что это сообщение является командой Bind.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Имя целевого портала (пустая строка выбирает безымянный портал).

String
Имя исходного подготовленного оператора (пустая строка выбирает безымянный подготовленный оператор).

Int16
Количество следующих кодов форматов параметров (обозначается ниже символом C). Может равняться нулю, показывая, что параметров нет или во всех параметрах используется формат по умолчанию (текстовый); или единице, и в этом случает заданный код формата применяется ко всем параметрам; или может равняться фактическому количеству параметров.

Int16[C]
Коды форматов параметров. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат).

Int16
Количество следующих значений параметров (может быть нулевым). Оно должно совпадать с количеством параметров, необходимых для запроса.

Затем для каждого параметра передается следующая пара полей:

Int32 Длина значения параметра в байтах (само поле длины не учитывается). Может быть нулевой. Как частный случай, -1 представляет значение параметра, равное NULL. В сценарии с NULL никакие байты значений далее не следуют.

Byten
Значение параметра в формате, определенном связанным кодом формата. n задает длину значения (см. выше).

После последнего параметра передаются следующие поля:

Int16
Количество следующих кодов форматов столбцов результата (обозначается ниже символом R). Может равняться нулю, показывая, что столбцов результата нет или во всех столбцах результата используется формат по умолчанию (текстовый); или единице, и в этом случает заданный код формата применяется ко всем столбцам результата (если таковые имеются); или может равняться фактическому количеству столбцов результата запроса.

Int16[R]
Коды форматов столбцов результата. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат).

BindComplete (B)

Byte1('2')
Показывает, что это сообщение сигнализирует о завершении Bind.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

CancelRequest (F)

Int32(16)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(80877102)
Код запроса отмены. Это выбранное значение, содержащее 1234 в старших 16 битах и 5678 в младших 16 битах. (Во избежание путаницы этот код не должен совпадать с номером версии протокола.)

Int32
PID целевого серверного процесса.

Int32
Секретный ключ для целевого серверного процесса.

Close (F)

Byte1('C')
Показывает, что это сообщение является командой Close.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Byte1 'S' для закрытия подготовленного оператора или 'P' для закрытия портала.

String Имя закрываемого подготовленного оператора или портала (пустая строка выбирает безымянный подготовленный оператор или портал).

CloseComplete (B)

Byte1('3')
Показывает, что это сообщение сигнализирует о завершении Close.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

CommandComplete (B)

Byte1('C')
Показывает, что это сообщение является ответом о завершении команды.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Тег команды. Обычно это одно слово, показывающее, какая именно команда SQL завершилась.

Для команды INSERT тегом является INSERT oid число_строк, где число_строк — это количество добавленных строк. В поле oid раньше указывался идентификатор объекта добавленной строки, если число_строк равнялось 1 и в целевой таблице имелись OID, но OID системных столбцов больше не поддерживаются, поэтому сейчас oid всегда равен 0.

Для команды DELETE тегом является DELETE число_строк, где число_строк — это количество удаленных строк.

Для команды UPDATE тегом является UPDATE число_строк, где число_строк — это количество измененных строк.

Для команды SELECT или CREATE TABLE AS тегом является SELECT число_строк, где число_строк — это количество полученных строк.

Для команды MOVE тегом является MOVE число_строк, где число_строк — это количество строк, на которое изменилась позиция курсора.

Для команды FETCH тегом является FETCH число_строк, где число_строк — это количество строк, полученных через курсор.

Для команды COPY тегом является COPY число_строк, где число_строк — это количество скопированных строк.

CopyData (F & B)

Byte1('d')
Показывает, что это сообщение является данными COPY.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Byten
Данные, образующие часть потока данных COPY. Сообщения, передаваемые от сервера, всегда будут соответствовать отдельным строкам данных, но сообщения, передаваемые клиентами, могут разделять поток данных произвольным образом.

CopyDone (F & B)

Byte1('c')
Показывает, что это сообщение сигнализирует о завершении COPY.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

CopyFail (F)

Byte1('f')
Показывает, что это сообщение сигнализирует об ошибке в выполнении COPY.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Сообщение об ошибке, описывающее ее причину.

CopyInResponse (B)

Byte1('G')
Показывает, что это сообщение является ответом на запуск входящего копирования. Теперь клиент должен передать данные входящего копирования (если он не готов это сделать, он передает сообщение CopyFail).

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int8
Значение 0 показывает, что у всей операции COPY текстовый формат (строки разделены символами перевода строки, столбцы разделены символами-разделителями и т. д.). Значение 1 показывает, что у всей операции копирования двоичный формат (схожий с форматом DataRow). Дополнительную информацию см. на справочной странице команды COPY.

Int16
Количество столбцов в копируемых данных (обозначается ниже символом N).

Int16[N]
Коды форматов, которые должны использоваться для каждого столбца. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат). Если общий формат копирования текстовый, все эти коды должны быть нулевыми.

CopyOutResponse (B)

Byte1('H')
Показывает, что это сообщение является ответом на запуск исходящего копирования. За этим сообщением последуют данные исходящего копирования.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int8
Значение 0 показывает, что у всей операции COPY текстовый формат (строки разделены символами перевода строки, столбцы разделены символами-разделителями и т. д.). Значение 1 показывает, что у всей операции копирования двоичный формат (схожий с форматом DataRow). Дополнительную информацию см. на справочной странице команды COPY.

Int16
Количество столбцов в копируемых данных (обозначается ниже символом N).

Int16[N]
Коды форматов, которые должны использоваться для каждого столбца. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат). Если общий формат копирования текстовый, все эти коды должны быть нулевыми.

CopyBothResponse (B)

Byte1('W')
Показывает, что это сообщение является ответом на запуск двустороннего копирования. Это сообщение используется только для потоковой репликации.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int8
Значение 0 показывает, что у всей операции COPY текстовый формат (строки разделены символами перевода строки, столбцы разделены символами-разделителями и т. д.). Значение 1 показывает, что у всей операции копирования двоичный формат (схожий с форматом DataRow). Дополнительную информацию см. на справочной странице команды COPY.

Int16
Количество столбцов в копируемых данных (обозначается ниже символом N).

Int16[N]
Коды форматов, которые должны использоваться для каждого столбца. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат). Если общий формат копирования текстовый, все эти коды должны быть нулевыми.

DataRow (B)

Byte1('D')
Показывает, что это сообщение является строкой данных.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int16
Количество последующих значений столбцов (может быть нулевым).

Затем для каждого столбца передается следующая пара полей:

Int32
Длина значения столбца в байтах (само поле длины не учитывается). Может быть нулевой. Как частный случай, -1 представляет значение столбца, равное NULL. В сценарии с NULL никакие байты значений далее не следуют.

Byten
Значение параметра в формате, определенном связанным кодом формата. n задает длину значения (см. выше).

Describe (F)

Byte1('D')
Показывает, что это сообщение является командой Describe.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Byte1 'S' для описания подготовленного оператора или 'P' для описания портала.

String
Имя описываемого подготовленного оператора или портала (пустая строка выбирает безымянный подготовленный оператор или портал).

EmptyQueryResponse (B)

Byte1('I')
Показывает, что это сообщение является ответом на пустую строку запроса. (Это сообщение заменяет CommandComplete.)

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

ErrorResponse (B)

Byte1('E') Показывает, что это сообщение ошибки.

Int32 Длина содержимого сообщения в байтах, включая само поле длины.

Тело сообщения состоит из одного или нескольких определенных полей, за которыми следует завершающий нулевой байт. Поля могут идти в любом порядке. Для каждого поля передается:

Byte1
Код, определяющий тип поля; ноль обозначает конец сообщения, после которого строк нет. Типы полей, определенные в настоящее время, перечислены в разделе Поля сообщений с ошибками и замечаниями. Поскольку в будущем могут быть добавлены другие типы полей, клиенты должны молча игнорировать поля нераспознанного типа.

String
Значение поля.

Execute (F)

Byte1('E') Показывает, что это сообщение является командой Execute.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Имя выполняемого портала (пустая строка выбирает безымянный портал).

Int32
Максимальное количество возвращаемых строк, если портал содержит запрос, возвращающий строки (в противном случае игнорируется). Ноль обозначает «без ограничений».

Flush (F)

Byte1('H')
Показывает, что это сообщение является командой Flush.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

FunctionCall (F)

Byte1('F')
Показывает, что это сообщение является вызовом функции.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32
Указывает идентификатор объекта вызываемой функции.

Int16
Количество следующих кодов форматов аргументов (обозначается ниже символом C). Может равняться нулю, показывая, что аргументов нет или во всех аргументах используется формат по умолчанию (текстовый); или единице, и в этом случает заданный код формата применяется ко всем аргументам; или может равняться фактическому количеству аргументов.

Int16[C]
Коды форматов аргументов. В настоящее время все они должны быть нулем (текстовый формат) или единицей (двоичный формат).

Int16
Указывает количество аргументов, передаваемых функции.

Затем для каждого аргумента передается следующая пара полей:

Int32
Длина значения аргумента в байтах (само поле длины не учитывается). Может быть нулевой. Как частный случай, -1 представляет значение аргумента, равное NULL. В сценарии с NULL никакие байты значений далее не следуют.

Byten
Значение аргумента в формате, определенном связанным кодом формата. n задает длину значения (см. выше).

После последнего аргумента передается следующее поле:

Int16
Код формата для результата функции. В настоящее время должен быть нулем (текстовый формат) или единицей (двоичный формат).

FunctionCallResponse (B)

Byte1('V')
Показывает, что это сообщение является результатом вызова функции.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32
Длина результата функции в байтах (само поле длины не учитывается). Может быть нулевой. Как частный случай, -1 представляет результат функции, равный NULL. В сценарии с NULL никакие байты значений далее не следуют.

Byten
Значение результата функции в формате, определенном связанным кодом формата. n задает длину значения (см. выше).

GSSENCRequest (F)

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(80877104)
Код запроса шифрования GSSAPI Это выбранное значение, содержащее 1234 в старших 16 битах и 5680 в младших 16 битах. (Во избежание путаницы этот код не должен совпадать с номером версии протокола.)

GSSResponse (F)

Byte1('p')
Показывает, что это сообщение является ответом GSSAPI. Обратите внимание, что оно также используется для ответных сообщений при аутентификации SASL и по паролю. Конкретный тип сообщения можно определить из контекста.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Byten
Данные сообщения, специфичные для GSSAPI.

NegotiateProtocolVersion (B)

Byte1('v')
Показывает, что это сообщение согласования версии протокола.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32
Новейшая дополнительная версия протокола, поддерживаемая сервером, для основной версии протокола, запрошенной клиентом.

Int32
Количество параметров протокола, не распознанных сервером.

Затем для параметров протокола, не распознанных сервером, передается следующее:

String
Имя протокола.

NoData (B)

Byte1('n')
Показывает, что это сообщение, сигнализирующее об отсутствии данных.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

NoticeResponse (B)

Byte1('N')
Показывает, что это сообщение является замечанием.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Тело сообщения состоит из одного или нескольких определенных полей, за которыми следует завершающий нулевой байт. Поля могут идти в любом порядке. Для каждого поля передается:

Byte1
Код, определяющий тип поля; ноль обозначает конец сообщения, после которого строк нет. Типы полей, определенные в настоящее время, перечислены в разделе Поля сообщений с ошибками и замечаниями. Поскольку в будущем могут быть добавлены другие типы полей, клиенты должны молча игнорировать поля нераспознанного типа.

String
Значение поля.

NotificationResponse (B)

Byte1('A')
Показывает, что это сообщение является ответом с уведомлением.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32
PID серверного процесса, передающего уведомление.

String
Имя канала, для которого было выдано уведомление.

String
Строка «полезной информации», передаваемая от уведомляющего процесса.

ParameterDescription (B)

Byte1('t')
Показывает, что это сообщение является описанием параметра.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int16
Количество параметров, используемых оператором (может быть нулевым).

Затем для каждого параметра передается следующее:

Int32
Указывает идентификатор объекта типа данных параметра.

ParameterStatus (B)

Byte1('S')
Показывает, что это сообщение является отчетом о состоянии параметра времени выполнения.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Имя параметра времени выполнения, о состоянии которого сообщается.

String
Текущее значение параметра.

Parse (F)

Byte1('P')
Показывает, что это сообщение является командой Parse.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Имя целевого подготовленного оператора (пустая строка выбирает безымянный подготовленный оператор).

String
Анализируемая строка запроса.

Int16
Количество указанных типов данных параметров (может быть нулевым). Обратите внимание, что это не обозначение количества параметров, которые могут находиться в строке запроса, а лишь количество параметров, для которых клиент хочет предопределить типы.

Затем для каждого параметра передается следующее:

Int32
Указывает идентификатор объекта типа данных параметра. Указание нулевого значения равнозначно отсутствию указания типа.

ParseComplete (B)

Byte1('1')
Показывает, что это сообщение, сигнализирующее о завершении Parse.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

PasswordMessage (F)

Byte1('p')
Показывает, что это сообщение является ответом с паролем. Обратите внимание, что оно также используется для ответных сообщений GSSAPI и SASL. Конкретный тип сообщения можно определить из контекста.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Пароль (зашифрованный, если требуется).

PortalSuspended (B)

Byte1('s')
Показывает, что это сообщение, сигнализирующее о приостановке портала. Обратите внимание, что оно выдается, только если был достигнут предел количества строк, указанного в сообщении Execute.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

Query (F)

Byte1('Q')
Показывает, что это сообщение является простым запросом.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Собственно строка запроса.

ReadyForQuery (B)

Byte1('Z')
Показывает тип сообщения. Сообщение ReadyForQuery передается, когда сервер готов к новому циклу запросов.

Int32(5)
Длина содержимого сообщения в байтах, включая само поле длины.

Byte1
Индикатор текущего состояния транзакции. Возможные значения: 'I', если транзакция неактивна (вне блока транзакции), 'T', если транзакция находится в блоке транзакции, или 'E', если транзакция находится в блоке неудавшейся транзакции (запросы будут отклоняться до конца блока).

RowDescription (B)

Byte1('T')
Показывает, что это сообщение является описанием строки.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int16
Указывает количество полей в строке (может быть нулевым).

Затем для каждого поля передается следующее:

String
Имя поля.

Int32
Если поле можно идентифицировать как столбец определенной таблицы, идентификатор объекта этой таблицы, в противном случае ноль.

Int16
Если поле можно идентифицировать как столбец определенной таблицы, номер атрибута для этого столбца, в противном случае ноль.

Int32
Идентификатор объекта типа данных поля.

Int16
Размер типа данных (см. pg_type.typlen). Обратите внимание, что отрицательные значения обозначают типы переменной длины.

Int32
Модификатор типа (см. pg_attribute.atttypmod). Смысл модификатора зависит от типа.

Int16
Код формата, используемого для поля. В настоящее время это будет ноль (текстовый формат) или единица (двоичный формат). В сообщении RowDescription, возвращаемом вариантом Describe оператора, код формата еще неизвестен и всегда будет нулевым.

SASLInitialResponse (F)

Byte1('p')
Показывает, что это сообщение является начальным ответом SASL. Обратите внимание, что оно также используется для ответных сообщений при аутентификации GSSAPI и по паролю. Конкретный тип сообщения определяется из контекста.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

String
Имя механизма аутентификации SASL, выбранного клиентом.

Int32
Длина последующего «Начального ответа клиента», специфичного для механизма SASL, или -1, если начальный ответ отсутствует.

Byten
«Начальный ответ», специфичный для механизма SASL.

SASLResponse (F)

Byte1('p')
Показывает, что это сообщение является ответом SASL. Обратите внимание, что оно также используется для ответных сообщений при аутентификации GSSAPI и по паролю. Конкретный тип сообщения определяется из контекста.

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Byten
Данные сообщения, специфичные для механизма SASL.

SSLRequest (F)

Int32(8)
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(80877103)
Код запроса SSL. Это выбранное значение, содержащее 1234 в старших 16 битах и 5679 в младших 16 битах. (Во избежание путаницы этот код не должен совпадать с номером версии протокола.)

StartupMessage (F)

Int32
Длина содержимого сообщения в байтах, включая само поле длины.

Int32(196608)
Номер версии протокола. Старшие 16 битов являются основным номером версии (3 для протокола, описываемого здесь). Младшие 16 битов являются дополнительным номером версии (0 для протокола, описываемого здесь).

За номером версии протокола следуют одна или несколько пар из имени параметра и строки значения. За последней парой имя/значение должен следовать завершающий нулевой байт. Параметры могут идти в любом порядке. Обязательным является параметр user, остальные необязательны. Каждый параметр задается так:

String
Имя параметра. В настоящее время распознаются имена:

user
Имя пользователя базы данных, с которым выполняется подключение. Является обязательным, значения по умолчанию нет.

database
База данных, к которой производится подключение. Значение по умолчанию — имя пользователя.

options
Аргументы командной строки для сервера. (Этот вариант считается устаревшим, и вместо него рекомендуется устанавливать отдельные параметры времени выполнения.) Пробелы в этой строке считаются разделителями между аргументами, если перед ними нет обратного слэша (\); чтобы представить обратный слэш буквально, напишите \\.

replication
Используется для подключения в режиме потоковой репликации, где вместо операторов SQL может выполняться небольшой набор команд репликации. Значения могут быть true, false (по умолчанию) или database. Подробную информацию см. в разделе Протокол потоковой репликации.

В дополнение вышеприведенным могут быть перечислены и другие параметры. Имена параметров, начинающиеся с _pq_., резервируются для использования в качестве расширений протокола, тогда как все остальные воспринимаются как параметры времени выполнения, устанавливаемые при запуске сервера. Такие параметры будут применяться во время запуска сервера (после анализа аргументов командной строки, если таковые имеются) и будут действовать как параметры сеанса по умолчанию.

String
Значение параметра.

Sync (F)

Byte1('S')
Показывает, что это сообщение является командой Sync.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.

Terminate (F)

Byte1('X')
Показывает, что это сообщение завершает сеанс.

Int32(4)
Длина содержимого сообщения в байтах, включая само поле длины.



Поля сообщений с ошибками и замечаниями

В этом разделе описываются поля, которые могут фигурировать в сообщениях ErrorResponse и NoticeResponse. У каждого типа поля имеется однобайтовый идентификационный маркер. Обратите внимание, что каждый отдельно взятый тип поля должен встречаться в сообщении не более одного раза.

S

Важность: это поле содержит ERROR, FATAL или PANIC (в сообщении об ошибке), либо WARNING, NOTICE, DEBUG, INFO или LOG (в сообщении с замечанием), либо их переведенные значения (ОШИБКА, ФАТАЛЬНО, ПАНИКА, ПРЕДУПРЕЖДЕНИЕ, ЗАМЕЧАНИЕ, ОТЛАДКА, ИНФОРМАЦИЯ, СООБЩЕНИЕ соответственно). Присутствует всегда.

V

Важность: это поле содержит ERROR, FATAL или PANIC (в сообщении об ошибке), либо WARNING, NOTICE, DEBUG, INFO или LOG (в сообщении с замечанием). Оно подобно полю S, но его содержимое никогда не переводится. Присутствует всегда.

C

Код: код SQLSTATE выданной ошибки (см. раздел Коды ошибок QHB). Не переводится на другие языки. Присутствует всегда.

M

Сообщение: основное сообщение об ошибке, удобное для восприятия человеком. Оно должно быть точным, но кратким (обычно в одну строку). Присутствует всегда.

D

Детализация: необязательное дополнительное сообщение об ошибке, содержащее более детальную информацию о проблеме. Может состоять из нескольких строк.

H

Подсказка: необязательное предложение решения проблемы. Оно должно отличаться от сообщения с детализацией тем, что предлагает совет (не исключено, что неподходящий), а не голые факты. Может состоять из нескольких строк.

P

Позиция: значение этого поля является целочисленным числом в ASCII, указывающим на положение курсора ошибки как на индекс в исходной строке запроса. Индекс первого символа — 1, при этом позиции отсчитываются по символам, а не по байтам.

p

Внутренняя позиция: она определяется так же, как поле P, но применяется, когда положение курсора относится ко внутренне сгенерированной команде, а не к команде, отправленной клиентом. Вместе с этим полем всегда присутствует поле q.

q

Внутренний запрос: текст внутренне сгенерированной команды, в которой произошла ошибка. Это может быть, например, запрос SQL, выполняемый функцией на PL/pgSQL.

W

Где: указание на контекст, в котором произошла ошибка. В настоящее время включает обратную трассировку стека активных функций на процедурном языке и внутренне сгенерированных запросов. Записи трассировки разделяются по строкам, вначале идет последняя.

s

Имя схемы: если ошибка связана с определенным объектом базы данных, в этом поле указывается имя схемы, содержащей этот объект, если таковая имеется.

t

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

c

Имя столбца: если ошибка связана с определенным столбцом таблицы, в этом поле указывается имя этого столбца. (Идентифицировать таблицу можно, обратившись к полям, содержащим имя схемы и таблицы.)

d

Имя типа данных: если ошибка связана с определенным типом данных, в этом поле указывается имя этого типа данных. (Узнать имя схемы типа данных можно, обратившись к соответствующему полю.)

n

Имя ограничения: если ошибка связана с определенным ограничением, в этом поле указывается имя этого ограничения. Узнать, к какой таблице или домену оно относится, можно, обратившись к полям, описанным выше. (В данном случае индексы считаются ограничениями, даже если они были созданы не с синтаксисом ограничений.)

F

Файл: имя файла в исходном коде, в котором была обнаружена ошибка.

L

Строка: номер строки в исходном коде, в которой была обнаружена ошибка.

R

Программа: имя программы в исходном коде, в которой была обнаружена ошибка.

Примечание
Поля, содержащие имена схемы, таблицы, столбца, типа данных и ограничения, выдаются только для ограниченного числа типов ошибок; см. раздел Коды ошибок QHB. Клиенты не должны рассчитывать на то, что присутствие одного из этих полей гарантирует присутствие другого поля. Системные источники ошибок устанавливают связь между ними, но пользовательские функции могут использовать эти поля по-другому. Аналогичным образом, клиенты не должны рассчитывать, что эти поля указывают на актуальные объекты в текущей базе данных.

Клиент отвечает за форматирование отображаемой информации в соответствии со своими нуждами; в частности, он должен разбивать длинные строки, как требуется. Символы перевода строки, встречающиеся в полях сообщения об ошибке, должны восприниматься как разрывы абзацев, а не строк.



Форматы сообщений логической репликации

В этом разделе подробно описывается формат всех сообщений логической репликации. Эти сообщения либо возвращаются через SQL-интерфейс слота репликации, либо передаются передатчиком WAL. В варианте с передатчиком WAL они заключаются внутрь WAL-сообщений протокола репликации, описанных в разделе Протокол потоковой репликации и в целом следуют тому же потоку сообщений, что и при физической репликации.

Begin

Byte1('B')
Показывает, что это начальное сообщение.

Int64
Конечный LSN транзакции.

Int64
Метка времени фиксации транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Int32
Идентификатор транзакции.

Message

Byte1('M')
Показывает, что это сообщение логического декодирования.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int8
Флаги; либо 0 при отсутствии флагов, либо 1, если сообщение логического декодирования является транзакционным.

Int64
LSN сообщения логического декодирования.

String
Префикс сообщения логического декодирования.

Int32
Длина содержимого.

Byten
Содержимое сообщения логического декодирования.

Commit

Byte1('C')
Показывает, что это сообщение о фиксации.

Int8
Флаги; в настоящее время не используются (поле должно содержать 0).

Int64
LSN фиксации.

Int64
Конечный LSN транзакции.

Int64
Метка времени фиксации транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Origin

Byte1('O')
Показывает, что это сообщение об источнике.

Int64
LSN фиксации на сервере-источнике.

String
Имя источника.

Обратите внимание, что внутри одной транзакции может быть несколько сообщений Origin.

Relation

Byte1('R')
Показывает, что это сообщение об отношении.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Идентификатор отношения.

String
Пространство имен (пустая строка для pg_catalog).

String
Имя отношения.

Int8
Идентификационная характеристика реплики для отношения (то же, что и столбец relreplident в pg_class).

Int16
Количество столбцов.

Затем для каждого столбца (кроме генерируемых) идет следующая часть сообщения:

Int8
Флаги для столбца. В настоящее время это может быть 0, означающий, что флагов нет, или 1, который помечает столбец как часть ключа.

String
Имя столбца.

Int32
Идентификатор типа данных столбца.

Int32
Модификатор типа столбца (atttypmod).

Type

Byte1('Y')
Показывает, что это сообщение о типе.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Идентификатор типа данных.

String
Пространство имен (пустая строка для pg_catalog).

String
Имя типа данных.

Insert

Byte1('I')
Показывает, что это сообщение о добавлении данных.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Идентификатор отношения, соответствующий идентификатору в сообщении об отношении.

Byte1('N')
Обозначает следующее сообщение TupleData как новый кортеж.

TupleData
Раздел сообщения TupleData, представляющий содержимое нового кортежа.

Update

Byte1('U')
Показывает, что это сообщение об изменении данных.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Идентификатор отношения, соответствующий идентификатору в сообщении об отношении.

Byte1('K')
Определяет следующее вложенное сообщение TupleData как ключ. Это поле является необязательным и присутствует, только если изменение затронуло данные в одном или нескольких столбцах, являющихся частью индекса REPLICA IDENTITY.

Byte1('O')
Определяет следующее вложенное сообщение TupleData как старый кортеж. Это поле является необязательным и присутствует, только если у таблицы, в которой произошло изменение, свойство REPLICA IDENTITY равно FULL.

TupleData
Раздел сообщения TupleData, представляющий содержимое старого кортежа или первичного ключа. Присутствует только при наличии перед ним раздела 'O' или 'K'.

Byte1('N')
Определяет следующее сообщение TupleData как новый кортеж.

TupleData
Раздел сообщения TupleData, представляющий содержимое нового кортежа.

Сообщение Update может содержать либо раздел 'K', либо раздел 'O', либо ни один из них, но не оба сразу.

Delete

Byte1('D')
Показывает, что это сообщение об удалении данных.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Идентификатор отношения, соответствующий идентификатору в сообщении об отношении.

Byte1('K')
Определяет следующее вложенное сообщение TupleData как ключ. Это поле присутствует, только если таблица, в которой произошло удаление, использует индекс в качестве REPLICA IDENTITY.

Byte1('O')
Определяет следующее вложенное сообщение TupleData как старый кортеж. Это поле присутствует, только если у таблицы, в которой произошло удаление, свойство REPLICA IDENTITY равно FULL.

TupleData
Раздел сообщения TupleData, представляющий содержимое старого кортежа или первичного ключа, в зависимости от предыдущего поля.

Сообщение Delete может содержать либо раздел 'K', либо раздел 'O', либо ни один из них, но не оба сразу.

Truncate

Byte1('T')
Показывает, что это сообщение об опустошении отношений.

Int32
Идентификатор транзакции (присутствует только для потоковых транзакций). Это поле доступно со 2-й версии протокола.

Int32
Количество отношений.

Int8
Битовые параметры для TRUNCATE: 1 для указания CASCADE, 2 для указания RESTART IDENTITY.

Int32
Идентификатор отношения, соответствующий идентификатору в сообщении об отношении. Это поле повторяется для каждого отношения.

Следующие сообщения (Stream Start, Stream Stop, Stream Commit, and Stream Abort) доступны со 2-й версии протокола.

Stream Start

Byte1('S')
Показывает, что это сообщение о начале потока.

Int32
Идентификатор транзакции.

Int8
Значение 1 показывает, что это первый сегмент потока с этим идентификатором; 0 для любого другого сегмента потока.

Stream Stop

Byte1('E')
Показывает, что это сообщение об окончании потока.

Stream Commit

Byte1('c')
Показывает, что это сообщение о фиксации транзакции в потоке.

Int32
Идентификатор транзакции.

Int8
Флаги; в настоящее время не используются (поле должно содержать 0).

Int64
LSN фиксации.

Int64
Конечный LSN транзакции.

Int64
Метка времени фиксации транзакции. Значение является количеством микросекунд, прошедших с начала эпохи QHB (2000-01-01).

Stream Abort

Byte1('A')
Показывает, что это сообщение о прерывании транзакции в потоке.

Int32
Идентификатор транзакции.

Int32
Идентификатор субтранзакции (для транзакций верхнего уровня будет совпадать с идентификатором транзакций).

Описанные выше сообщения имеют следующие общие разделы.

TupleData

Int16
Количество столбцов.

Затем для каждого столбца (кроме генерируемых) идет одно из следующих вложенных сообщений:

Byte1('n')
Обозначает данные как значение NULL.

Или

Byte1('u')
Обозначает неизмененное значение TOAST (само значение не передается).

Или

Byte1('t')
Обозначает данные как значение в текстовом формате.

Или

Byte1('b')
Обозначает данные как значение в двоичном формате.

Int32
Длина значения столбца.

Byten
Значение столбца в двоичном или текстовом формате (определяется предшествующим байтом формата). n задает длину значения (см. выше).



Сводка изменений по сравнению с протоколом версии 2.0

В этом разделе представлен краткий список изменений, который может быть полезен разработчикам, желающим обновить существующие клиентские библиотеки до протокола 3.0.

В начальном пакете автозапуска вместо фиксированного формата используется гибкий формат списка строк. Обратите внимание, что сеансовые значения по умолчанию для параметров времени выполнения теперь можно задать непосредственно в пакете автозапуска. (На самом деле это можно было сделать и раньше, используя поле options, но, учитывая ограниченную ширину options и отсутствие способа заключить пробелы в значениях в кавычки, этот метод был не очень безопасным.)

Теперь в всех сообщениях сразу за байтом типа сообщения идет счетчик длины (за исключением пакетов автозапуска, в которых нет байта типа). Также обратите внимание, что байт типа теперь есть в сообщении PasswordMessage.

Сообщения ErrorResponse и NoticeResponse ('E' и 'N') теперь содержат несколько полей, из которых клиентский код может собрать сообщение об ошибке желаемого уровня детализации. Обратите внимание, что отдельные поля скорее всего не будут завершаться переводом строки, тогда как в старом протоколе одиночная строка всегда завершалась так.

Сообщение ReadyForQuery ('Z') включает индикатор статуса транзакции.

Различие между типами сообщений BinaryRow и DataRow исчезло; один тип сообщений DataRow служит для возвращения данных во всех форматах. Обратите внимание, что структура DataRow была изменена для упрощения его синтаксического анализа. Кроме того, изменилось представление двоичных значений: оно больше не привязано к внутреннему представлению сервера.

В протоколе появился новый подчиненный «протокол расширенных запросов», который добавляет типы клиентских сообщений Parse, Bind, Execute, Describe, Close, Flush и Sync, а также типы серверных сообщений ParseComplete, BindComplete, PortalSuspended, ParameterDescription, NoData и CloseComplete. Существующие клиенты не обязаны подстраиваться под этот подчиненный протокол, но вполне вероятно, что его применение позволит улучшить производительность или функциональность.

Данные COPY теперь заключаются в сообщения CopyData и CopyDone. Существует четко определенный способ восстановления в случае ошибок в процессе выполнения COPY. Специальная последняя строка «\.» больше не нужна и не передается во время COPY OUT. (Она по-прежнему распознается как завершающая при COPY IN, но считается устаревшей и в итоге будет убрана.) Поддерживается COPY в двоичном режиме. Сообщения CopyInResponse и CopyOutResponse включают поля, показывающие количество столбцов и формат каждого столбца.

Изменилась структура сообщений FunctionCall и FunctionCallResponse. Сообщение FunctionCall теперь поддерживает передачу функциям аргументов NULL. Также в нем могут передаваться параметры и возвращаться результаты в текстовом или двоичном формате. Больше нет причины считать сообщение FunctionCall потенциальной брешью в безопасности, поскольку оно не дает прямого доступа к внутренней презентации данных на сервере.

Сервер передает сообщения ParameterStatus ('S') при запуске подключения для всех параметров, которые он считает интересными для клиентской библиотеки. Впоследствии при любом изменении активного значения одного из этих параметров тоже передается сообщение ParameterStatus.

Сообщение RowDescription ('T') содержит новые поля с OID таблицы и номером столбца для каждого столбца описываемой строки. Кроме того, в нем выводится код формата для каждого столбца.

Сервер больше не генерирует сообщение CursorResponse ('P').

В сообщение NotificationResponse ('A') добавилось строковое поле, в котором может содержаться строка «полезных данных», передаваемая отправителем события NOTIFY.

Раньше сообщение EmptyQueryResponse ('I') включало пустой строковый параметр; он был удален.